CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-baomidou--mybatis-plus-extension

MyBatis-Plus Extension module providing advanced features including service layers, Kotlin extensions, SQL parsers, caching mechanisms, and various interceptors for enhanced ORM functionality

Pending
Overview
Eval results
Files

plugin-system.mddocs/

Plugin System

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.

Core Components

MybatisPlusInterceptor

The main interceptor that coordinates all inner interceptors.

public class MybatisPlusInterceptor implements Interceptor {
    private List<InnerInterceptor> interceptors = new ArrayList<>();
    
    public void addInnerInterceptor(InnerInterceptor innerInterceptor);
    public List<InnerInterceptor> getInterceptors();
    public void setInterceptors(List<InnerInterceptor> interceptors);
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable;
    
    @Override
    public Object plugin(Object target);
    
    @Override
    public void setProperties(Properties properties);
}

InnerInterceptor Interface

Base interface for all inner interceptors providing lifecycle hooks.

public interface InnerInterceptor {
    
    // Query lifecycle hooks
    default void willDoQuery(Executor executor, MappedStatement ms, Object parameter, 
                           RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) 
                           throws SQLException {}
    
    default void beforeQuery(Executor executor, MappedStatement ms, Object parameter, 
                           RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) 
                           throws SQLException {}
    
    // Update lifecycle hooks
    default void willDoUpdate(Executor executor, MappedStatement ms, Object parameter) 
                            throws SQLException {}
    
    default void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) 
                            throws SQLException {}
    
    // Statement lifecycle hooks
    default void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {}
    
    default void beforeGetBoundSql(StatementHandler sh) {}
    
    // Configuration
    default void setProperties(Properties properties) {}
}

Built-in Interceptors

PaginationInnerInterceptor

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

public class PaginationInnerInterceptor implements InnerInterceptor {
    private DbType dbType;
    private IDialect dialect;
    private boolean optimizeJoin = true;
    private Long maxLimit;
    private boolean overflow = false;
    
    public PaginationInnerInterceptor();
    public PaginationInnerInterceptor(DbType dbType);
    
    // Configuration methods
    public void setDbType(DbType dbType);
    public void setDialect(IDialect dialect);
    public void setOptimizeJoin(boolean optimizeJoin);
    public void setMaxLimit(Long maxLimit);
    public void setOverflow(boolean overflow);
}

TenantLineInnerInterceptor

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

public class TenantLineInnerInterceptor implements InnerInterceptor {
    private TenantLineHandler tenantLineHandler;
    
    public TenantLineInnerInterceptor();
    public TenantLineInnerInterceptor(TenantLineHandler tenantLineHandler);
    
    public void setTenantLineHandler(TenantLineHandler tenantLineHandler);
    public TenantLineHandler getTenantLineHandler();
}

public interface TenantLineHandler {
    Expression getTenantId();
    String getTenantIdColumn();
    boolean ignoreTable(String tableName);
    boolean ignoreInsert(List<Column> columns, String tenantIdColumn);
}

DynamicTableNameInnerInterceptor

Enables dynamic table name replacement at runtime.

public class DynamicTableNameInnerInterceptor implements InnerInterceptor {
    private Map<String, TableNameHandler> tableNameHandlerMap = new HashMap<>();
    
    public void setTableNameHandler(String tableName, TableNameHandler handler);
    public void setTableNameHandlerMap(Map<String, TableNameHandler> tableNameHandlerMap);
}

public interface TableNameHandler {
    String dynamicTableName(String sql, String tableName);
}

OptimisticLockerInnerInterceptor

Implements optimistic locking by automatically handling version fields.

public class OptimisticLockerInnerInterceptor implements InnerInterceptor {
    
    // No public configuration methods - works with @Version annotation
}

BlockAttackInnerInterceptor

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

public class BlockAttackInnerInterceptor implements InnerInterceptor {
    
    // Automatically blocks dangerous operations
}

IllegalSQLInnerInterceptor

Detects and blocks illegal SQL patterns.

public class IllegalSQLInnerInterceptor implements InnerInterceptor {
    
    // Configurable illegal SQL detection
}

DataPermissionInterceptor

Implements data-level permissions by modifying SQL queries.

public class DataPermissionInterceptor implements InnerInterceptor {
    private DataPermissionHandler dataPermissionHandler;
    
    public DataPermissionInterceptor();
    public DataPermissionInterceptor(DataPermissionHandler dataPermissionHandler);
    
    public void setDataPermissionHandler(DataPermissionHandler dataPermissionHandler);
}

public interface DataPermissionHandler {
    Expression getSqlSegment(Table table, Expression where, String mappedStatementId);
}

DataChangeRecorderInnerInterceptor

Records data changes for audit trails.

public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {
    
    // Automatically records data changes
}

ReplacePlaceholderInnerInterceptor

Replaces custom placeholders in SQL statements.

public class ReplacePlaceholderInnerInterceptor implements InnerInterceptor {
    
    // Placeholder replacement functionality
}

Configuration Examples

Basic Configuration

@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // Add pagination support
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        
        // Add optimistic locking
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        
        // Block dangerous operations
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        
        return interceptor;
    }
}

Multi-Tenant Configuration

@Component
public class CustomTenantHandler implements TenantLineHandler {
    
    @Override
    public Expression getTenantId() {
        // Get current tenant ID from security context
        String tenantId = SecurityContextHolder.getCurrentTenantId();
        return new StringValue(tenantId);
    }
    
    @Override
    public String getTenantIdColumn() {
        return "tenant_id";
    }
    
    @Override
    public boolean ignoreTable(String tableName) {
        // Ignore system tables
        return "system_config".equals(tableName) || "global_settings".equals(tableName);
    }
    
    @Override
    public boolean ignoreInsert(List<Column> columns, String tenantIdColumn) {
        // Check if tenant_id is already provided
        return columns.stream().anyMatch(column -> 
            tenantIdColumn.equals(column.getColumnName()));
    }
}

@Configuration
public class TenantConfig {
    
    @Autowired
    private CustomTenantHandler tenantHandler;
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // Add tenant line interceptor
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantHandler));
        
        return interceptor;
    }
}

Dynamic Table Name Configuration

@Configuration
public class DynamicTableConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        DynamicTableNameInnerInterceptor dynamicTableNameInterceptor = 
            new DynamicTableNameInnerInterceptor();
        
        // Configure table name handlers
        dynamicTableNameInterceptor.setTableNameHandler("user", (sql, tableName) -> {
            // Route to different tables based on date
            String month = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
            return tableName + "_" + month;
        });
        
        dynamicTableNameInterceptor.setTableNameHandler("order", (sql, tableName) -> {
            // Route based on tenant
            String tenantId = getCurrentTenantId();
            return "tenant_" + tenantId + "_" + tableName;
        });
        
        interceptor.addInnerInterceptor(dynamicTableNameInterceptor);
        
        return interceptor;
    }
    
    private String getCurrentTenantId() {
        // Implementation to get current tenant
        return "default";
    }
}

Data Permission Configuration

@Component
public class CustomDataPermissionHandler implements DataPermissionHandler {
    
    @Override
    public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
        // Apply data permissions based on current user role
        String currentRole = getCurrentUserRole();
        String tableName = table.getName();
        
        if ("user".equals(tableName) && !"ADMIN".equals(currentRole)) {
            // Non-admin users can only see their own data
            String currentUserId = getCurrentUserId();
            Expression userCondition = new EqualsTo(
                new Column("created_by"), 
                new StringValue(currentUserId)
            );
            
            if (where != null) {
                return new AndExpression(where, userCondition);
            } else {
                return userCondition;
            }
        }
        
        return where;
    }
    
    private String getCurrentUserRole() {
        // Get from security context
        return "USER";
    }
    
    private String getCurrentUserId() {
        // Get from security context
        return "user123";
    }
}

@Configuration
public class DataPermissionConfig {
    
    @Autowired
    private CustomDataPermissionHandler dataPermissionHandler;
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        interceptor.addInnerInterceptor(
            new DataPermissionInterceptor(dataPermissionHandler));
        
        return interceptor;
    }
}

Advanced Pagination Configuration

@Configuration
public class PaginationConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        PaginationInnerInterceptor paginationInterceptor = 
            new PaginationInnerInterceptor(DbType.MYSQL);
        
        // Configure pagination options
        paginationInterceptor.setMaxLimit(1000L); // Max page size
        paginationInterceptor.setOverflow(false); // Don't allow overflow
        paginationInterceptor.setOptimizeJoin(true); // Optimize count queries
        
        interceptor.addInnerInterceptor(paginationInterceptor);
        
        return interceptor;
    }
}

Usage Examples

Using with Optimistic Locking

@TableName("user")
public class User {
    @TableId
    private Long id;
    
    private String name;
    
    @Version
    private Integer version; // Optimistic lock field
    
    // getters and setters
}

// Usage
User user = userService.getById(1L);
user.setName("Updated Name");
boolean updated = userService.updateById(user); // Version automatically handled

Pagination Usage

// Pagination is automatically applied when using Page parameter
Page<User> page = new Page<>(1, 10);
Page<User> result = userService.page(page, 
    new QueryWrapper<User>().eq("active", true));

// Get results
List<User> users = result.getRecords();
long total = result.getTotal(); // Total count automatically calculated
long pages = result.getPages(); // Total pages calculated

Multi-Tenant Usage

// With tenant interceptor configured, all queries automatically include tenant condition
List<User> users = userService.list(); // Automatically filtered by tenant_id

// Insert automatically adds tenant_id
User newUser = new User("John", "john@example.com");
userService.save(newUser); // tenant_id automatically added

Dynamic Table Names

// With dynamic table name handler configured
// Calls to user table are automatically routed to user_202401, user_202402, etc.
List<User> users = userService.list(); // Uses dynamic table name

Custom Interceptor Development

Creating Custom Interceptor

public class CustomAuditInterceptor implements InnerInterceptor {
    
    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {
        // Add audit fields automatically
        if (parameter instanceof Map) {
            Map<String, Object> params = (Map<String, Object>) parameter;
            params.put("updatedBy", getCurrentUserId());
            params.put("updatedTime", LocalDateTime.now());
        }
    }
    
    @Override
    public void willDoQuery(Executor executor, MappedStatement ms, Object parameter, 
                           RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // Log all queries for debugging
        System.out.println("Executing query: " + boundSql.getSql());
    }
    
    private String getCurrentUserId() {
        // Get current user from security context
        return "current_user";
    }
}

Registering Custom Interceptor

@Configuration
public class CustomInterceptorConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // Add custom interceptor
        interceptor.addInnerInterceptor(new CustomAuditInterceptor());
        
        // Add other interceptors
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        
        return interceptor;
    }
}

Interceptor Order

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

  1. TenantLineInnerInterceptor - Should be first to ensure tenant filtering
  2. DynamicTableNameInnerInterceptor - Early to handle table name resolution
  3. PaginationInnerInterceptor - Middle for pagination logic
  4. OptimisticLockerInnerInterceptor - Late for version handling
  5. BlockAttackInnerInterceptor - Last for security validation
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    
    // Order matters!
    interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(tenantHandler));
    interceptor.addInnerInterceptor(new DynamicTableNameInnerInterceptor());
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
    
    return interceptor;
}

Install with Tessl CLI

npx tessl i tessl/maven-com-baomidou--mybatis-plus-extension

docs

active-record.md

condition-builders.md

index.md

kotlin-extensions.md

pagination.md

plugin-system.md

service-layer.md

static-utilities.md

tile.json