or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

active-record.mdcondition-builders.mdindex.mdkotlin-extensions.mdpagination.mdplugin-system.mdservice-layer.mdstatic-utilities.md

plugin-system.mddocs/

0

# Plugin System

1

2

MyBatis-Plus Extension provides a powerful interceptor-based plugin system that enables cross-cutting concerns like pagination, multi-tenancy, data permissions, optimistic locking, and SQL security features. The plugin system is built around the `MybatisPlusInterceptor` main interceptor and various `InnerInterceptor` implementations.

3

4

## Core Components

5

6

### MybatisPlusInterceptor

7

8

The main interceptor that coordinates all inner interceptors.

9

10

```java { .api }

11

public class MybatisPlusInterceptor implements Interceptor {

12

private List<InnerInterceptor> interceptors = new ArrayList<>();

13

14

public void addInnerInterceptor(InnerInterceptor innerInterceptor);

15

public List<InnerInterceptor> getInterceptors();

16

public void setInterceptors(List<InnerInterceptor> interceptors);

17

18

@Override

19

public Object intercept(Invocation invocation) throws Throwable;

20

21

@Override

22

public Object plugin(Object target);

23

24

@Override

25

public void setProperties(Properties properties);

26

}

27

```

28

29

### InnerInterceptor Interface

30

31

Base interface for all inner interceptors providing lifecycle hooks.

32

33

```java { .api }

34

public interface InnerInterceptor {

35

36

// Query lifecycle hooks

37

default void willDoQuery(Executor executor, MappedStatement ms, Object parameter,

38

RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)

39

throws SQLException {}

40

41

default void beforeQuery(Executor executor, MappedStatement ms, Object parameter,

42

RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)

43

throws SQLException {}

44

45

// Update lifecycle hooks

46

default void willDoUpdate(Executor executor, MappedStatement ms, Object parameter)

47

throws SQLException {}

48

49

default void beforeUpdate(Executor executor, MappedStatement ms, Object parameter)

50

throws SQLException {}

51

52

// Statement lifecycle hooks

53

default void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {}

54

55

default void beforeGetBoundSql(StatementHandler sh) {}

56

57

// Configuration

58

default void setProperties(Properties properties) {}

59

}

60

```

61

62

## Built-in Interceptors

63

64

### PaginationInnerInterceptor

65

66

Provides automatic pagination support with count queries and limit/offset handling.

67

68

```java { .api }

69

public class PaginationInnerInterceptor implements InnerInterceptor {

70

private DbType dbType;

71

private IDialect dialect;

72

private boolean optimizeJoin = true;

73

private Long maxLimit;

74

private boolean overflow = false;

75

76

public PaginationInnerInterceptor();

77

public PaginationInnerInterceptor(DbType dbType);

78

79

// Configuration methods

80

public void setDbType(DbType dbType);

81

public void setDialect(IDialect dialect);

82

public void setOptimizeJoin(boolean optimizeJoin);

83

public void setMaxLimit(Long maxLimit);

84

public void setOverflow(boolean overflow);

85

}

86

```

87

88

### TenantLineInnerInterceptor

89

90

Implements multi-tenant line-level security by automatically adding tenant conditions to SQL.

91

92

```java { .api }

93

public class TenantLineInnerInterceptor implements InnerInterceptor {

94

private TenantLineHandler tenantLineHandler;

95

96

public TenantLineInnerInterceptor();

97

public TenantLineInnerInterceptor(TenantLineHandler tenantLineHandler);

98

99

public void setTenantLineHandler(TenantLineHandler tenantLineHandler);

100

public TenantLineHandler getTenantLineHandler();

101

}

102

103

public interface TenantLineHandler {

104

Expression getTenantId();

105

String getTenantIdColumn();

106

boolean ignoreTable(String tableName);

107

boolean ignoreInsert(List<Column> columns, String tenantIdColumn);

108

}

109

```

110

111

### DynamicTableNameInnerInterceptor

112

113

Enables dynamic table name replacement at runtime.

114

115

```java { .api }

116

public class DynamicTableNameInnerInterceptor implements InnerInterceptor {

117

private Map<String, TableNameHandler> tableNameHandlerMap = new HashMap<>();

118

119

public void setTableNameHandler(String tableName, TableNameHandler handler);

120

public void setTableNameHandlerMap(Map<String, TableNameHandler> tableNameHandlerMap);

121

}

122

123

public interface TableNameHandler {

124

String dynamicTableName(String sql, String tableName);

125

}

126

```

127

128

### OptimisticLockerInnerInterceptor

129

130

Implements optimistic locking by automatically handling version fields.

131

132

```java { .api }

133

public class OptimisticLockerInnerInterceptor implements InnerInterceptor {

134

135

// No public configuration methods - works with @Version annotation

136

}

137

```

138

139

### BlockAttackInnerInterceptor

140

141

Blocks potentially dangerous SQL operations like full-table updates or deletes.

142

143

```java { .api }

144

public class BlockAttackInnerInterceptor implements InnerInterceptor {

145

146

// Automatically blocks dangerous operations

147

}

148

```

149

150

### IllegalSQLInnerInterceptor

151

152

Detects and blocks illegal SQL patterns.

153

154

```java { .api }

155

public class IllegalSQLInnerInterceptor implements InnerInterceptor {

156

157

// Configurable illegal SQL detection

158

}

159

```

160

161

### DataPermissionInterceptor

162

163

Implements data-level permissions by modifying SQL queries.

164

165

```java { .api }

166

public class DataPermissionInterceptor implements InnerInterceptor {

167

private DataPermissionHandler dataPermissionHandler;

168

169

public DataPermissionInterceptor();

170

public DataPermissionInterceptor(DataPermissionHandler dataPermissionHandler);

171

172

public void setDataPermissionHandler(DataPermissionHandler dataPermissionHandler);

173

}

174

175

public interface DataPermissionHandler {

176

Expression getSqlSegment(Table table, Expression where, String mappedStatementId);

177

}

178

```

179

180

### DataChangeRecorderInnerInterceptor

181

182

Records data changes for audit trails.

183

184

```java { .api }

185

public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {

186

187

// Automatically records data changes

188

}

189

```

190

191

### ReplacePlaceholderInnerInterceptor

192

193

Replaces custom placeholders in SQL statements.

194

195

```java { .api }

196

public class ReplacePlaceholderInnerInterceptor implements InnerInterceptor {

197

198

// Placeholder replacement functionality

199

}

200

```

201

202

## Configuration Examples

203

204

### Basic Configuration

205

206

```java

207

@Configuration

208

public class MybatisPlusConfig {

209

210

@Bean

211

public MybatisPlusInterceptor mybatisPlusInterceptor() {

212

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

213

214

// Add pagination support

215

interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

216

217

// Add optimistic locking

218

interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

219

220

// Block dangerous operations

221

interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());

222

223

return interceptor;

224

}

225

}

226

```

227

228

### Multi-Tenant Configuration

229

230

```java

231

@Component

232

public class CustomTenantHandler implements TenantLineHandler {

233

234

@Override

235

public Expression getTenantId() {

236

// Get current tenant ID from security context

237

String tenantId = SecurityContextHolder.getCurrentTenantId();

238

return new StringValue(tenantId);

239

}

240

241

@Override

242

public String getTenantIdColumn() {

243

return "tenant_id";

244

}

245

246

@Override

247

public boolean ignoreTable(String tableName) {

248

// Ignore system tables

249

return "system_config".equals(tableName) || "global_settings".equals(tableName);

250

}

251

252

@Override

253

public boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {

254

// Check if tenant_id is already provided

255

return columns.stream().anyMatch(column ->

256

tenantIdColumn.equals(column.getColumnName()));

257

}

258

}

259

260

@Configuration

261

public class TenantConfig {

262

263

@Autowired

264

private CustomTenantHandler tenantHandler;

265

266

@Bean

267

public MybatisPlusInterceptor mybatisPlusInterceptor() {

268

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

269

270

// Add tenant line interceptor

271

interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantHandler));

272

273

return interceptor;

274

}

275

}

276

```

277

278

### Dynamic Table Name Configuration

279

280

```java

281

@Configuration

282

public class DynamicTableConfig {

283

284

@Bean

285

public MybatisPlusInterceptor mybatisPlusInterceptor() {

286

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

287

288

DynamicTableNameInnerInterceptor dynamicTableNameInterceptor =

289

new DynamicTableNameInnerInterceptor();

290

291

// Configure table name handlers

292

dynamicTableNameInterceptor.setTableNameHandler("user", (sql, tableName) -> {

293

// Route to different tables based on date

294

String month = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMM"));

295

return tableName + "_" + month;

296

});

297

298

dynamicTableNameInterceptor.setTableNameHandler("order", (sql, tableName) -> {

299

// Route based on tenant

300

String tenantId = getCurrentTenantId();

301

return "tenant_" + tenantId + "_" + tableName;

302

});

303

304

interceptor.addInnerInterceptor(dynamicTableNameInterceptor);

305

306

return interceptor;

307

}

308

309

private String getCurrentTenantId() {

310

// Implementation to get current tenant

311

return "default";

312

}

313

}

314

```

315

316

### Data Permission Configuration

317

318

```java

319

@Component

320

public class CustomDataPermissionHandler implements DataPermissionHandler {

321

322

@Override

323

public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {

324

// Apply data permissions based on current user role

325

String currentRole = getCurrentUserRole();

326

String tableName = table.getName();

327

328

if ("user".equals(tableName) && !"ADMIN".equals(currentRole)) {

329

// Non-admin users can only see their own data

330

String currentUserId = getCurrentUserId();

331

Expression userCondition = new EqualsTo(

332

new Column("created_by"),

333

new StringValue(currentUserId)

334

);

335

336

if (where != null) {

337

return new AndExpression(where, userCondition);

338

} else {

339

return userCondition;

340

}

341

}

342

343

return where;

344

}

345

346

private String getCurrentUserRole() {

347

// Get from security context

348

return "USER";

349

}

350

351

private String getCurrentUserId() {

352

// Get from security context

353

return "user123";

354

}

355

}

356

357

@Configuration

358

public class DataPermissionConfig {

359

360

@Autowired

361

private CustomDataPermissionHandler dataPermissionHandler;

362

363

@Bean

364

public MybatisPlusInterceptor mybatisPlusInterceptor() {

365

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

366

367

interceptor.addInnerInterceptor(

368

new DataPermissionInterceptor(dataPermissionHandler));

369

370

return interceptor;

371

}

372

}

373

```

374

375

### Advanced Pagination Configuration

376

377

```java

378

@Configuration

379

public class PaginationConfig {

380

381

@Bean

382

public MybatisPlusInterceptor mybatisPlusInterceptor() {

383

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

384

385

PaginationInnerInterceptor paginationInterceptor =

386

new PaginationInnerInterceptor(DbType.MYSQL);

387

388

// Configure pagination options

389

paginationInterceptor.setMaxLimit(1000L); // Max page size

390

paginationInterceptor.setOverflow(false); // Don't allow overflow

391

paginationInterceptor.setOptimizeJoin(true); // Optimize count queries

392

393

interceptor.addInnerInterceptor(paginationInterceptor);

394

395

return interceptor;

396

}

397

}

398

```

399

400

## Usage Examples

401

402

### Using with Optimistic Locking

403

404

```java

405

@TableName("user")

406

public class User {

407

@TableId

408

private Long id;

409

410

private String name;

411

412

@Version

413

private Integer version; // Optimistic lock field

414

415

// getters and setters

416

}

417

418

// Usage

419

User user = userService.getById(1L);

420

user.setName("Updated Name");

421

boolean updated = userService.updateById(user); // Version automatically handled

422

```

423

424

### Pagination Usage

425

426

```java

427

// Pagination is automatically applied when using Page parameter

428

Page<User> page = new Page<>(1, 10);

429

Page<User> result = userService.page(page,

430

new QueryWrapper<User>().eq("active", true));

431

432

// Get results

433

List<User> users = result.getRecords();

434

long total = result.getTotal(); // Total count automatically calculated

435

long pages = result.getPages(); // Total pages calculated

436

```

437

438

### Multi-Tenant Usage

439

440

```java

441

// With tenant interceptor configured, all queries automatically include tenant condition

442

List<User> users = userService.list(); // Automatically filtered by tenant_id

443

444

// Insert automatically adds tenant_id

445

User newUser = new User("John", "john@example.com");

446

userService.save(newUser); // tenant_id automatically added

447

```

448

449

### Dynamic Table Names

450

451

```java

452

// With dynamic table name handler configured

453

// Calls to user table are automatically routed to user_202401, user_202402, etc.

454

List<User> users = userService.list(); // Uses dynamic table name

455

```

456

457

## Custom Interceptor Development

458

459

### Creating Custom Interceptor

460

461

```java

462

public class CustomAuditInterceptor implements InnerInterceptor {

463

464

@Override

465

public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {

466

// Add audit fields automatically

467

if (parameter instanceof Map) {

468

Map<String, Object> params = (Map<String, Object>) parameter;

469

params.put("updatedBy", getCurrentUserId());

470

params.put("updatedTime", LocalDateTime.now());

471

}

472

}

473

474

@Override

475

public void willDoQuery(Executor executor, MappedStatement ms, Object parameter,

476

RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

477

// Log all queries for debugging

478

System.out.println("Executing query: " + boundSql.getSql());

479

}

480

481

private String getCurrentUserId() {

482

// Get current user from security context

483

return "current_user";

484

}

485

}

486

```

487

488

### Registering Custom Interceptor

489

490

```java

491

@Configuration

492

public class CustomInterceptorConfig {

493

494

@Bean

495

public MybatisPlusInterceptor mybatisPlusInterceptor() {

496

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

497

498

// Add custom interceptor

499

interceptor.addInnerInterceptor(new CustomAuditInterceptor());

500

501

// Add other interceptors

502

interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

503

504

return interceptor;

505

}

506

}

507

```

508

509

## Interceptor Order

510

511

The order in which interceptors are added matters. Common ordering recommendations:

512

513

1. **TenantLineInnerInterceptor** - Should be first to ensure tenant filtering

514

2. **DynamicTableNameInnerInterceptor** - Early to handle table name resolution

515

3. **PaginationInnerInterceptor** - Middle for pagination logic

516

4. **OptimisticLockerInnerInterceptor** - Late for version handling

517

5. **BlockAttackInnerInterceptor** - Last for security validation

518

519

```java

520

@Bean

521

public MybatisPlusInterceptor mybatisPlusInterceptor() {

522

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

523

524

// Order matters!

525

interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantHandler));

526

interceptor.addInnerInterceptor(new DynamicTableNameInnerInterceptor());

527

interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

528

interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

529

interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());

530

531

return interceptor;

532

}

533

```