MyBatis-Plus is an enhanced toolkit for MyBatis providing CRUD operations, query wrappers, pagination, code generation, and Spring Boot integration.
—
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.
Main interceptor that manages inner interceptors for different functionalities.
/**
* MyBatis-Plus main interceptor
*/
public class MybatisPlusInterceptor implements Interceptor {
/**
* Add inner interceptor
* @param innerInterceptor Inner interceptor to add
*/
public void addInnerInterceptor(InnerInterceptor innerInterceptor);
/**
* Get all inner interceptors
* @return List of inner interceptors
*/
public List<InnerInterceptor> getInterceptors();
/**
* Set properties for configuration
* @param properties Configuration properties
*/
public void setProperties(Properties properties);
}Base interface for all inner interceptors.
/**
* Inner interceptor interface
*/
public interface InnerInterceptor {
/**
* Check if query should be intercepted
* @param executor MyBatis executor
* @param ms Mapped statement
* @param parameter Query parameters
* @param rowBounds Row bounds
* @param resultHandler Result handler
* @param boundSql Bound SQL
* @return true to intercept
*/
default boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
return true;
}
/**
* Before query execution
* @param executor MyBatis executor
* @param ms Mapped statement
* @param parameter Query parameters
* @param rowBounds Row bounds
* @param resultHandler Result handler
* @param boundSql Bound SQL
*/
default void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// Implementation specific
}
/**
* Check if update should be intercepted
* @param executor MyBatis executor
* @param ms Mapped statement
* @param parameter Update parameters
* @return true to intercept
*/
default boolean willDoUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
return true;
}
/**
* Before update execution
* @param executor MyBatis executor
* @param ms Mapped statement
* @param parameter Update parameters
*/
default void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) throws SQLException {
// Implementation specific
}
}Automatic pagination support with count query optimization.
/**
* Pagination interceptor
*/
public class PaginationInnerInterceptor implements InnerInterceptor {
/**
* Default constructor
*/
public PaginationInnerInterceptor();
/**
* Constructor with database type
* @param dbType Database type
*/
public PaginationInnerInterceptor(DbType dbType);
/**
* Set maximum limit for single query
* @param maxLimit Maximum limit
*/
public void setMaxLimit(Long maxLimit);
/**
* Set overflow handling
* @param overflow Whether to handle page overflow
*/
public void setOverflow(boolean overflow);
/**
* Set whether to optimize count query
* @param optimizeCountSql Optimization flag
*/
public void setOptimizeCountSql(boolean optimizeCountSql);
}Optimistic locking support using version fields.
/**
* Optimistic locking interceptor
*/
public class OptimisticLockerInnerInterceptor implements InnerInterceptor {
/**
* Default constructor
*/
public OptimisticLockerInnerInterceptor();
}Multi-tenant support with automatic tenant line filtering.
/**
* Tenant line interceptor for multi-tenant applications
*/
public class TenantLineInnerInterceptor implements InnerInterceptor {
/**
* Constructor with tenant line handler
* @param tenantLineHandler Tenant line handler
*/
public TenantLineInnerInterceptor(TenantLineHandler tenantLineHandler);
/**
* Set tenant line handler
* @param tenantLineHandler Tenant line handler
*/
public void setTenantLineHandler(TenantLineHandler tenantLineHandler);
}
/**
* Tenant line handler interface
*/
public interface TenantLineHandler {
/**
* Get tenant ID
* @return Current tenant ID
*/
Expression getTenantId();
/**
* Get tenant ID column name
* @return Tenant ID column name
*/
String getTenantIdColumn();
/**
* Check if table should ignore tenant filtering
* @param tableName Table name
* @return true to ignore tenant filtering
*/
default boolean ignoreTable(String tableName) {
return false;
}
/**
* Check if INSERT should ignore tenant column
* @param columns Insert columns
* @param tenantIdColumn Tenant ID column
* @return true to ignore
*/
default boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
return false;
}
}Dynamic table name support for table sharding scenarios.
/**
* Dynamic table name interceptor
*/
public class DynamicTableNameInnerInterceptor implements InnerInterceptor {
/**
* Constructor with table name handler
* @param tableNameHandler Table name handler
*/
public DynamicTableNameInnerInterceptor(TableNameHandler tableNameHandler);
/**
* Set table name handler
* @param tableNameHandler Table name handler
*/
public void setTableNameHandler(TableNameHandler tableNameHandler);
}
/**
* Table name handler interface
*/
public interface TableNameHandler {
/**
* Dynamic table name replacement
* @param sql Original SQL
* @param tableName Original table name
* @return New table name
*/
String dynamicTableName(String sql, String tableName);
}Protection against dangerous SQL operations.
/**
* Block attack interceptor to prevent dangerous operations
*/
public class BlockAttackInnerInterceptor implements InnerInterceptor {
/**
* Default constructor
*/
public BlockAttackInnerInterceptor();
}Detection and blocking of illegal SQL patterns.
/**
* Illegal SQL interceptor
*/
public class IllegalSQLInnerInterceptor implements InnerInterceptor {
/**
* Default constructor
*/
public IllegalSQLInnerInterceptor();
}Audit interceptor for tracking data changes with before/after values, user information, and timestamps.
/**
* Data change recording interceptor for audit trails
*/
public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {
/**
* Default constructor
*/
public DataChangeRecorderInnerInterceptor();
/**
* Constructor with custom data change handler
* @param dataChangeHandler Custom handler for processing data changes
*/
public DataChangeRecorderInnerInterceptor(DataChangeHandler dataChangeHandler);
/**
* Set custom data change handler
* @param dataChangeHandler Handler for processing data changes
*/
public void setDataChangeHandler(DataChangeHandler dataChangeHandler);
}
/**
* Handler interface for processing data changes
*/
public interface DataChangeHandler {
/**
* Handle data change event
* @param changeRecord Data change record containing details
*/
void handleDataChange(DataChangeRecord changeRecord);
}
/**
* Data change record containing audit information
*/
public class DataChangeRecord {
private String tableName;
private String operation; // INSERT, UPDATE, DELETE
private Object primaryKey;
private Map<String, Object> beforeData;
private Map<String, Object> afterData;
private String userId;
private LocalDateTime changeTime;
private String changeReason;
// getters and setters...
}Basic Interceptor Configuration:
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// Add pagination interceptor
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// Add optimistic locking interceptor
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// Add block attack interceptor
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}Pagination Configuration:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// Set maximum limit to prevent large queries
paginationInterceptor.setMaxLimit(1000L);
// Enable overflow handling (redirect to first page if page exceeds total)
paginationInterceptor.setOverflow(true);
// Optimize count queries
paginationInterceptor.setOptimizeCountSql(true);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}Multi-Tenant Configuration:
@Component
public class CustomTenantLineHandler implements TenantLineHandler {
@Override
public Expression getTenantId() {
// Get current tenant ID from security context, session, etc.
String tenantId = TenantContextHolder.getCurrentTenantId();
return new LongValue(Long.parseLong(tenantId));
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean ignoreTable(String tableName) {
// Ignore tenant filtering for system tables
return Arrays.asList("sys_config", "sys_dict", "sys_log").contains(tableName);
}
@Override
public boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
// Check if tenant column already exists in INSERT
return columns.stream().anyMatch(column ->
column.getColumnName().equalsIgnoreCase(tenantIdColumn));
}
}
@Configuration
public class MybatisPlusConfig {
@Autowired
private CustomTenantLineHandler tenantLineHandler;
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// Add tenant line interceptor
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantLineHandler));
return interceptor;
}
}Dynamic Table Name Configuration:
@Component
public class CustomTableNameHandler implements TableNameHandler {
@Override
public String dynamicTableName(String sql, String tableName) {
// Table sharding by date
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}"
},
{
"old_string": "",
"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."
}]
}
]Install with Tessl CLI
npx tessl i tessl/maven-com-baomidou--mybatis-plus