or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

active-record.mdannotations.mdcode-generation.mdcore-crud.mdindex.mdpagination.mdplugins.mdquery-building.mdservice-layer.mdspring-boot.md

plugins.mddocs/

0

# Plugins and Interceptors

1

2

MyBatis-Plus provides an extensible plugin system for adding cross-cutting functionality like pagination, optimistic locking, tenant isolation, SQL security, and performance monitoring through interceptors.

3

4

## Capabilities

5

6

### MybatisPlusInterceptor

7

8

Main interceptor that manages inner interceptors for different functionalities.

9

10

```java { .api }

11

/**

12

* MyBatis-Plus main interceptor

13

*/

14

public class MybatisPlusInterceptor implements Interceptor {

15

16

/**

17

* Add inner interceptor

18

* @param innerInterceptor Inner interceptor to add

19

*/

20

public void addInnerInterceptor(InnerInterceptor innerInterceptor);

21

22

/**

23

* Get all inner interceptors

24

* @return List of inner interceptors

25

*/

26

public List<InnerInterceptor> getInterceptors();

27

28

/**

29

* Set properties for configuration

30

* @param properties Configuration properties

31

*/

32

public void setProperties(Properties properties);

33

}

34

```

35

36

### InnerInterceptor Interface

37

38

Base interface for all inner interceptors.

39

40

```java { .api }

41

/**

42

* Inner interceptor interface

43

*/

44

public interface InnerInterceptor {

45

46

/**

47

* Check if query should be intercepted

48

* @param executor MyBatis executor

49

* @param ms Mapped statement

50

* @param parameter Query parameters

51

* @param rowBounds Row bounds

52

* @param resultHandler Result handler

53

* @param boundSql Bound SQL

54

* @return true to intercept

55

*/

56

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

57

RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {

58

return true;

59

}

60

61

/**

62

* Before query execution

63

* @param executor MyBatis executor

64

* @param ms Mapped statement

65

* @param parameter Query parameters

66

* @param rowBounds Row bounds

67

* @param resultHandler Result handler

68

* @param boundSql Bound SQL

69

*/

70

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

71

RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {

72

// Implementation specific

73

}

74

75

/**

76

* Check if update should be intercepted

77

* @param executor MyBatis executor

78

* @param ms Mapped statement

79

* @param parameter Update parameters

80

* @return true to intercept

81

*/

82

default boolean willDoUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {

83

return true;

84

}

85

86

/**

87

* Before update execution

88

* @param executor MyBatis executor

89

* @param ms Mapped statement

90

* @param parameter Update parameters

91

*/

92

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

93

// Implementation specific

94

}

95

}

96

```

97

98

### Core Interceptors

99

100

#### PaginationInnerInterceptor

101

102

Automatic pagination support with count query optimization.

103

104

```java { .api }

105

/**

106

* Pagination interceptor

107

*/

108

public class PaginationInnerInterceptor implements InnerInterceptor {

109

110

/**

111

* Default constructor

112

*/

113

public PaginationInnerInterceptor();

114

115

/**

116

* Constructor with database type

117

* @param dbType Database type

118

*/

119

public PaginationInnerInterceptor(DbType dbType);

120

121

/**

122

* Set maximum limit for single query

123

* @param maxLimit Maximum limit

124

*/

125

public void setMaxLimit(Long maxLimit);

126

127

/**

128

* Set overflow handling

129

* @param overflow Whether to handle page overflow

130

*/

131

public void setOverflow(boolean overflow);

132

133

/**

134

* Set whether to optimize count query

135

* @param optimizeCountSql Optimization flag

136

*/

137

public void setOptimizeCountSql(boolean optimizeCountSql);

138

}

139

```

140

141

#### OptimisticLockerInnerInterceptor

142

143

Optimistic locking support using version fields.

144

145

```java { .api }

146

/**

147

* Optimistic locking interceptor

148

*/

149

public class OptimisticLockerInnerInterceptor implements InnerInterceptor {

150

151

/**

152

* Default constructor

153

*/

154

public OptimisticLockerInnerInterceptor();

155

}

156

```

157

158

#### TenantLineInnerInterceptor

159

160

Multi-tenant support with automatic tenant line filtering.

161

162

```java { .api }

163

/**

164

* Tenant line interceptor for multi-tenant applications

165

*/

166

public class TenantLineInnerInterceptor implements InnerInterceptor {

167

168

/**

169

* Constructor with tenant line handler

170

* @param tenantLineHandler Tenant line handler

171

*/

172

public TenantLineInnerInterceptor(TenantLineHandler tenantLineHandler);

173

174

/**

175

* Set tenant line handler

176

* @param tenantLineHandler Tenant line handler

177

*/

178

public void setTenantLineHandler(TenantLineHandler tenantLineHandler);

179

}

180

181

/**

182

* Tenant line handler interface

183

*/

184

public interface TenantLineHandler {

185

186

/**

187

* Get tenant ID

188

* @return Current tenant ID

189

*/

190

Expression getTenantId();

191

192

/**

193

* Get tenant ID column name

194

* @return Tenant ID column name

195

*/

196

String getTenantIdColumn();

197

198

/**

199

* Check if table should ignore tenant filtering

200

* @param tableName Table name

201

* @return true to ignore tenant filtering

202

*/

203

default boolean ignoreTable(String tableName) {

204

return false;

205

}

206

207

/**

208

* Check if INSERT should ignore tenant column

209

* @param columns Insert columns

210

* @param tenantIdColumn Tenant ID column

211

* @return true to ignore

212

*/

213

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

214

return false;

215

}

216

}

217

```

218

219

#### DynamicTableNameInnerInterceptor

220

221

Dynamic table name support for table sharding scenarios.

222

223

```java { .api }

224

/**

225

* Dynamic table name interceptor

226

*/

227

public class DynamicTableNameInnerInterceptor implements InnerInterceptor {

228

229

/**

230

* Constructor with table name handler

231

* @param tableNameHandler Table name handler

232

*/

233

public DynamicTableNameInnerInterceptor(TableNameHandler tableNameHandler);

234

235

/**

236

* Set table name handler

237

* @param tableNameHandler Table name handler

238

*/

239

public void setTableNameHandler(TableNameHandler tableNameHandler);

240

}

241

242

/**

243

* Table name handler interface

244

*/

245

public interface TableNameHandler {

246

247

/**

248

* Dynamic table name replacement

249

* @param sql Original SQL

250

* @param tableName Original table name

251

* @return New table name

252

*/

253

String dynamicTableName(String sql, String tableName);

254

}

255

```

256

257

#### BlockAttackInnerInterceptor

258

259

Protection against dangerous SQL operations.

260

261

```java { .api }

262

/**

263

* Block attack interceptor to prevent dangerous operations

264

*/

265

public class BlockAttackInnerInterceptor implements InnerInterceptor {

266

267

/**

268

* Default constructor

269

*/

270

public BlockAttackInnerInterceptor();

271

}

272

```

273

274

#### IllegalSQLInnerInterceptor

275

276

Detection and blocking of illegal SQL patterns.

277

278

```java { .api }

279

/**

280

* Illegal SQL interceptor

281

*/

282

public class IllegalSQLInnerInterceptor implements InnerInterceptor {

283

284

/**

285

* Default constructor

286

*/

287

public IllegalSQLInnerInterceptor();

288

}

289

```

290

291

#### DataChangeRecorderInnerInterceptor (MyBatis-Plus 3.5.7+)

292

293

Audit interceptor for tracking data changes with before/after values, user information, and timestamps.

294

295

```java { .api }

296

/**

297

* Data change recording interceptor for audit trails

298

*/

299

public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {

300

301

/**

302

* Default constructor

303

*/

304

public DataChangeRecorderInnerInterceptor();

305

306

/**

307

* Constructor with custom data change handler

308

* @param dataChangeHandler Custom handler for processing data changes

309

*/

310

public DataChangeRecorderInnerInterceptor(DataChangeHandler dataChangeHandler);

311

312

/**

313

* Set custom data change handler

314

* @param dataChangeHandler Handler for processing data changes

315

*/

316

public void setDataChangeHandler(DataChangeHandler dataChangeHandler);

317

}

318

319

/**

320

* Handler interface for processing data changes

321

*/

322

public interface DataChangeHandler {

323

324

/**

325

* Handle data change event

326

* @param changeRecord Data change record containing details

327

*/

328

void handleDataChange(DataChangeRecord changeRecord);

329

}

330

331

/**

332

* Data change record containing audit information

333

*/

334

public class DataChangeRecord {

335

private String tableName;

336

private String operation; // INSERT, UPDATE, DELETE

337

private Object primaryKey;

338

private Map<String, Object> beforeData;

339

private Map<String, Object> afterData;

340

private String userId;

341

private LocalDateTime changeTime;

342

private String changeReason;

343

344

// getters and setters...

345

}

346

```

347

348

## Usage Examples

349

350

**Basic Interceptor Configuration:**

351

352

```java

353

@Configuration

354

public class MybatisPlusConfig {

355

356

@Bean

357

public MybatisPlusInterceptor mybatisPlusInterceptor() {

358

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

359

360

// Add pagination interceptor

361

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

362

363

// Add optimistic locking interceptor

364

interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

365

366

// Add block attack interceptor

367

interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());

368

369

return interceptor;

370

}

371

}

372

```

373

374

**Pagination Configuration:**

375

376

```java

377

@Bean

378

public MybatisPlusInterceptor mybatisPlusInterceptor() {

379

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

380

381

PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);

382

383

// Set maximum limit to prevent large queries

384

paginationInterceptor.setMaxLimit(1000L);

385

386

// Enable overflow handling (redirect to first page if page exceeds total)

387

paginationInterceptor.setOverflow(true);

388

389

// Optimize count queries

390

paginationInterceptor.setOptimizeCountSql(true);

391

392

interceptor.addInnerInterceptor(paginationInterceptor);

393

394

return interceptor;

395

}

396

```

397

398

**Multi-Tenant Configuration:**

399

400

```java

401

@Component

402

public class CustomTenantLineHandler implements TenantLineHandler {

403

404

@Override

405

public Expression getTenantId() {

406

// Get current tenant ID from security context, session, etc.

407

String tenantId = TenantContextHolder.getCurrentTenantId();

408

return new LongValue(Long.parseLong(tenantId));

409

}

410

411

@Override

412

public String getTenantIdColumn() {

413

return "tenant_id";

414

}

415

416

@Override

417

public boolean ignoreTable(String tableName) {

418

// Ignore tenant filtering for system tables

419

return Arrays.asList("sys_config", "sys_dict", "sys_log").contains(tableName);

420

}

421

422

@Override

423

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

424

// Check if tenant column already exists in INSERT

425

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

426

column.getColumnName().equalsIgnoreCase(tenantIdColumn));

427

}

428

}

429

430

@Configuration

431

public class MybatisPlusConfig {

432

433

@Autowired

434

private CustomTenantLineHandler tenantLineHandler;

435

436

@Bean

437

public MybatisPlusInterceptor mybatisPlusInterceptor() {

438

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

439

440

// Add tenant line interceptor

441

interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantLineHandler));

442

443

return interceptor;

444

}

445

}

446

```

447

448

**Dynamic Table Name Configuration:**

449

450

```java

451

@Component

452

public class CustomTableNameHandler implements TableNameHandler {

453

454

@Override

455

public String dynamicTableName(String sql, String tableName) {

456

// Table sharding by date

457

if (\"user_log\".equals(tableName)) {\n String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern(\"yyyyMM\"));\n return tableName + \"_\" + datePrefix; // user_log_202312\n }\n \n // Table sharding by tenant\n if (\"order\".equals(tableName)) {\n String tenantId = TenantContextHolder.getCurrentTenantId();\n return tableName + \"_\" + tenantId; // order_tenant_001\n }\n \n return tableName;\n }\n}\n\n@Configuration\npublic class MybatisPlusConfig {\n \n @Autowired\n private CustomTableNameHandler tableNameHandler;\n \n @Bean\n public MybatisPlusInterceptor mybatisPlusInterceptor() {\n MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();\n \n // Add dynamic table name interceptor\n interceptor.addInnerInterceptor(new DynamicTableNameInnerInterceptor(tableNameHandler));\n \n return interceptor;\n }\n}"

458

},

459

{

460

"old_string": "",

461

"new_string": "**Optimistic Locking Usage:**\n\n```java\n// Entity with version field\n@TableName(\"product\")\npublic class Product {\n @TableId(type = IdType.AUTO)\n private Long id;\n \n private String name;\n private BigDecimal price;\n \n @Version // Optimistic locking version field\n private Integer version;\n \n // getters and setters...\n}\n\n// Service method with optimistic locking\n@Service\npublic class ProductService {\n \n @Autowired\n private ProductMapper productMapper;\n \n public boolean updateProductPrice(Long productId, BigDecimal newPrice) {\n // Get current product with version\n Product product = productMapper.selectById(productId);\n if (product == null) {\n return false;\n }\n \n // Update price\n product.setPrice(newPrice);\n \n // Update will automatically check version and increment it\n int result = productMapper.updateById(product);\n \n // result = 0 means version conflict (concurrent update)\n return result > 0;\n }\n}\n```\n\n**Custom Interceptor Development:**\n\n```java\n@Component\npublic class SqlLoggingInterceptor implements InnerInterceptor {\n \n private static final Logger logger = LoggerFactory.getLogger(SqlLoggingInterceptor.class);\n \n @Override\n public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,\n RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {\n logSqlExecution(\"QUERY\", ms.getId(), boundSql.getSql(), parameter);\n }\n \n @Override\n public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {\n BoundSql boundSql = ms.getBoundSql(parameter);\n logSqlExecution(\"UPDATE\", ms.getId(), boundSql.getSql(), parameter);\n }\n \n private void logSqlExecution(String type, String mapperId, String sql, Object parameter) {\n if (logger.isDebugEnabled()) {\n logger.debug(\"[{}] Mapper: {}\", type, mapperId);\n logger.debug(\"[{}] SQL: {}\", type, sql.replaceAll(\"\\\\s+\", \" \").trim());\n logger.debug(\"[{}] Parameters: {}\", type, parameter);\n }\n }\n}\n\n// Register custom interceptor\n@Configuration\npublic class MybatisPlusConfig {\n \n @Autowired\n private SqlLoggingInterceptor sqlLoggingInterceptor;\n \n @Bean\n public MybatisPlusInterceptor mybatisPlusInterceptor() {\n MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();\n \n // Add custom logging interceptor\n interceptor.addInnerInterceptor(sqlLoggingInterceptor);\n \n // Add other interceptors...\n \n return interceptor;\n }\n}\n```\n\n**Performance Monitoring Interceptor:**\n\n```java\n@Component\npublic class PerformanceMonitorInterceptor implements InnerInterceptor {\n \n private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitorInterceptor.class);\n private final ThreadLocal<Long> startTime = new ThreadLocal<>();\n \n @Override\n public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,\n RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {\n startTime.set(System.currentTimeMillis());\n }\n \n @Override\n public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {\n startTime.set(System.currentTimeMillis());\n }\n \n // Note: For complete implementation, you'd need to implement Interceptor interface directly\n // This is a simplified example showing the concept\n \n public void afterExecution(String mapperId) {\n Long start = startTime.get();\n if (start != null) {\n long duration = System.currentTimeMillis() - start;\n if (duration > 1000) { // Log slow queries (> 1 second)\n logger.warn(\"Slow query detected: {} took {}ms\", mapperId, duration);\n }\n startTime.remove();\n }\n }\n}\n```\n\n**Data Permission Interceptor:**\n\n```java\n@Component\npublic class DataPermissionInterceptor implements InnerInterceptor {\n \n @Override\n public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,\n RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {\n \n // Check if current user has permission to access data\n String userId = SecurityContextHolder.getCurrentUserId();\n String userRole = SecurityContextHolder.getCurrentUserRole();\n \n // Apply data filtering based on user permissions\n if (!\"ADMIN\".equals(userRole)) {\n // For non-admin users, add WHERE condition to limit data access\n String originalSql = boundSql.getSql();\n String permissionSql = addDataPermissionCondition(originalSql, userId, ms.getId());\n \n // Modify the bound SQL (implementation would require reflection)\n // This is a conceptual example\n }\n }\n \n private String addDataPermissionCondition(String originalSql, String userId, String mapperId) {\n // Add conditions based on business rules\n if (mapperId.contains(\"selectUser\")) {\n return originalSql + \" AND (created_by = '\" + userId + \"' OR assigned_to = '\" + userId + \"')\";\n }\n return originalSql;\n }\n}\n```\n\n**Interceptor Order and Priority:**\n\n```java\n@Configuration\npublic class MybatisPlusConfig {\n \n @Bean\n public MybatisPlusInterceptor mybatisPlusInterceptor() {\n MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();\n \n // Order matters! Interceptors are executed in the order they are added\n \n // 1. Multi-tenant filtering (should be first for security)\n interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantLineHandler));\n \n // 2. Data permission filtering\n interceptor.addInnerInterceptor(new DataPermissionInterceptor());\n \n // 3. Dynamic table name (for sharding)\n interceptor.addInnerInterceptor(new DynamicTableNameInnerInterceptor(tableNameHandler));\n \n // 4. Pagination (should be after filtering interceptors)\n interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));\n \n // 5. Optimistic locking (for update operations)\n interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());\n \n // 6. Security interceptors (should be after business logic)\n interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());\n interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());\n \n // 7. Monitoring and logging (should be last)\n interceptor.addInnerInterceptor(new PerformanceMonitorInterceptor());\n \n return interceptor;\n }\n}\n```\n\n**Conditional Interceptor Configuration:**\n\n```java\n@Configuration\npublic class MybatisPlusConfig {\n \n @Value(\"${mybatis-plus.interceptor.pagination.enabled:true}\")\n private boolean paginationEnabled;\n \n @Value(\"${mybatis-plus.interceptor.tenant.enabled:false}\")\n private boolean tenantEnabled;\n \n @Bean\n public MybatisPlusInterceptor mybatisPlusInterceptor() {\n MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();\n \n // Conditionally add interceptors based on configuration\n if (tenantEnabled) {\n interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantLineHandler));\n }\n \n if (paginationEnabled) {\n interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));\n }\n \n // Always add security interceptors in production\n if (isProductionEnvironment()) {\n interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());\n interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());\n }\n \n return interceptor;\n }\n \n private boolean isProductionEnvironment() {\n return \"production\".equals(System.getProperty(\"spring.profiles.active\"));\n }\n}\n```\n\nThis plugin system provides powerful extensibility for adding cross-cutting concerns to MyBatis-Plus operations while maintaining clean separation of business logic and infrastructure concerns."

462

}]

463

}

464

]