MyBatis-Plus插件体系:自定义拦截器实现AOP式数据处理的完整教程

MyBatis-Plus插件体系:自定义拦截器实现AOP式数据处理的完整教程

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-plus

引言:为什么需要自定义拦截器?

在日常开发中,你是否遇到过这些痛点场景:

  • 需要自动填充创建时间、更新时间等公共字段
  • 要实现数据权限控制,根据用户角色动态过滤查询结果
  • 需要对敏感数据进行加密存储和解密展示
  • 要实现多租户数据隔离,自动添加租户ID条件
  • 需要记录SQL执行日志进行性能监控

这些横切关注点(Cross-Cutting Concerns)如果散落在业务代码中,会导致代码重复、维护困难。MyBatis-Plus的插件体系提供了完美的AOP(Aspect-Oriented Programming)解决方案,让你能够以非侵入式的方式实现这些功能。

MyBatis-Plus插件架构解析

核心组件关系图

mermaid

拦截器执行流程

mermaid

自定义拦截器实战:数据自动填充拦截器

场景需求

我们需要在数据插入和更新时自动填充创建时间、更新时间、创建人、更新人等字段。

步骤1:定义实体类注解

import java.lang.annotation.*;

/**
 * 自动填充注解
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface AutoFill {
    
    /**
     * 填充类型
     */
    FillType value();
    
    enum FillType {
        /**
         * 插入时填充
         */
        INSERT,
        /**
         * 更新时填充
         */
        UPDATE,
        /**
         * 插入和更新时都填充
         */
        INSERT_UPDATE
    }
}

步骤2:实现自定义拦截器

package com.example.interceptor;

import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Invocation;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Date;

/**
 * 自动填充拦截器
 */
public class AutoFillInterceptor implements InnerInterceptor {
    
    private final FillValueProvider fillValueProvider;
    
    public AutoFillInterceptor(FillValueProvider fillValueProvider) {
        this.fillValueProvider = fillValueProvider;
    }
    
    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {
        if (SqlCommandType.INSERT == ms.getSqlCommandType() || 
            SqlCommandType.UPDATE == ms.getSqlCommandType()) {
            processAutoFill(parameter, ms.getSqlCommandType());
        }
    }
    
    private void processAutoFill(Object parameter, SqlCommandType commandType) {
        if (parameter instanceof Map) {
            Map<?, ?> paramMap = (Map<?, ?>) parameter;
            Object entity = paramMap.get("et");
            if (entity != null) {
                fillEntityFields(entity, commandType);
            }
        } else if (parameter != null) {
            fillEntityFields(parameter, commandType);
        }
    }
    
    private void fillEntityFields(Object entity, SqlCommandType commandType) {
        Class<?> entityClass = entity.getClass();
        Field[] fields = entityClass.getDeclaredFields();
        
        Arrays.stream(fields)
            .filter(field -> field.isAnnotationPresent(AutoFill.class))
            .forEach(field -> {
                AutoFill autoFill = field.getAnnotation(AutoFill.class);
                if (shouldFill(autoFill.value(), commandType)) {
                    fillField(entity, field, autoFill.value());
                }
            });
    }
    
    private boolean shouldFill(AutoFill.FillType fillType, SqlCommandType commandType) {
        if (SqlCommandType.INSERT == commandType) {
            return fillType == AutoFill.FillType.INSERT || 
                   fillType == AutoFill.FillType.INSERT_UPDATE;
        } else if (SqlCommandType.UPDATE == commandType) {
            return fillType == AutoFill.FillType.UPDATE || 
                   fillType == AutoFill.FillType.INSERT_UPDATE;
        }
        return false;
    }
    
    private void fillField(Object entity, Field field, AutoFill.FillType fillType) {
        try {
            field.setAccessible(true);
            Object currentValue = field.get(entity);
            
            // 如果字段已有值,则不填充
            if (currentValue != null) {
                return;
            }
            
            Object fillValue = getFillValue(field, fillType);
            if (fillValue != null) {
                field.set(entity, fillValue);
            }
        } catch (IllegalAccessException e) {
            throw new RuntimeException("自动填充字段失败: " + field.getName(), e);
        }
    }
    
    private Object getFillValue(Field field, AutoFill.FillType fillType) {
        Class<?> fieldType = field.getType();
        
        // 根据字段类型返回相应的填充值
        if (LocalDateTime.class.equals(fieldType)) {
            return LocalDateTime.now();
        } else if (Date.class.equals(fieldType)) {
            return new Date();
        } else if (String.class.equals(fieldType)) {
            return fillValueProvider.getCurrentUsername();
        } else if (Long.class.equals(fieldType) || long.class.equals(fieldType)) {
            return fillValueProvider.getCurrentUserId();
        }
        
        return null;
    }
    
    @Override
    public void setProperties(Properties properties) {
        // 可配置属性处理
    }
}

/**
 * 填充值提供者接口
 */
public interface FillValueProvider {
    String getCurrentUsername();
    Long getCurrentUserId();
}

步骤3:配置拦截器

@Configuration
public class MybatisPlusConfig {
    
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(FillValueProvider fillValueProvider) {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        
        // 添加自动填充拦截器
        interceptor.addInnerInterceptor(new AutoFillInterceptor(fillValueProvider));
        
        // 可以继续添加其他拦截器
        // interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        // interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        
        return interceptor;
    }
    
    @Bean
    public FillValueProvider fillValueProvider() {
        return new FillValueProvider() {
            @Override
            public String getCurrentUsername() {
                // 从安全上下文获取当前用户名
                return SecurityContextHolder.getContext().getAuthentication().getName();
            }
            
            @Override
            public Long getCurrentUserId() {
                // 从安全上下文获取当前用户ID
                User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
                return user.getId();
            }
        };
    }
}

步骤4:使用示例

/**
 * 用户实体类
 */
@Data
@TableName("sys_user")
public class User {
    
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String username;
    
    private String password;
    
    @AutoFill(AutoFill.FillType.INSERT)
    private LocalDateTime createTime;
    
    @AutoFill(AutoFill.FillType.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    @AutoFill(AutoFill.FillType.INSERT)
    private String createBy;
    
    @AutoFill(AutoFill.FillType.INSERT_UPDATE)
    private String updateBy;
}

// 使用示例
User user = new User();
user.setUsername("testuser");
user.setPassword("password123");
userService.save(user); // 自动填充createTime、updateTime、createBy、updateBy字段

高级应用:数据权限拦截器

场景需求

根据用户角色动态添加数据权限过滤条件。

数据权限拦截器实现

package com.example.interceptor;

import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Set;

/**
 * 数据权限拦截器
 */
public class DataPermissionInterceptor implements InnerInterceptor {
    
    private final DataPermissionProvider permissionProvider;
    
    public DataPermissionInterceptor(DataPermissionProvider permissionProvider) {
        this.permissionProvider = permissionProvider;
    }
    
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, 
                           RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        
        // 获取当前用户的数据权限规则
        DataPermissionRule rule = permissionProvider.getCurrentPermissionRule();
        if (rule == null || !rule.isEnabled()) {
            return;
        }
        
        // 解析原始SQL
        String originalSql = boundSql.getSql();
        String modifiedSql = applyDataPermission(originalSql, rule);
        
        // 使用反射修改BoundSql中的SQL
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, modifiedSql);
    }
    
    private String applyDataPermission(String sql, DataPermissionRule rule) {
        // 简单的SQL解析和条件添加
        // 实际项目中可以使用JSqlParser等SQL解析库
        
        String upperSql = sql.toUpperCase();
        if (upperSql.contains(" WHERE ")) {
            return sql.replace(" WHERE ", " WHERE " + rule.toSqlCondition() + " AND ");
        } else if (upperSql.contains(" WHERE\n")) {
            return sql.replace(" WHERE\n", " WHERE " + rule.toSqlCondition() + " AND\n");
        } else {
            // 找到FROM子句的位置
            int fromIndex = upperSql.indexOf(" FROM ");
            if (fromIndex != -1) {
                // 在FROM后添加WHERE条件
                int whereInsertIndex = fromIndex + 6; // " FROM "的长度
                return sql.substring(0, whereInsertIndex) + " WHERE " + 
                       rule.toSqlCondition() + sql.substring(whereInsertIndex);
            }
        }
        
        return sql;
    }
}

/**
 * 数据权限规则
 */
public class DataPermissionRule {
    private boolean enabled;
    private String tableName;
    private String columnName;
    private Set<String> allowedValues;
    
    public String toSqlCondition() {
        if (!enabled || allowedValues.isEmpty()) {
            return "1=1";
        }
        
        StringBuilder condition = new StringBuilder();
        condition.append(columnName).append(" IN (");
        
        int i = 0;
        for (String value : allowedValues) {
            if (i++ > 0) {
                condition.append(",");
            }
            condition.append("'").append(value).append("'");
        }
        
        condition.append(")");
        return condition.toString();
    }
    
    // getters and setters
}

性能优化与最佳实践

拦截器执行顺序管理

@Configuration
public class InterceptorOrderConfig {
    
    @Bean
    @Order(1)
    public InnerInterceptor dataPermissionInterceptor() {
        return new DataPermissionInterceptor();
    }
    
    @Bean
    @Order(2)
    public InnerInterceptor autoFillInterceptor() {
        return new AutoFillInterceptor();
    }
    
    @Bean
    @Order(3)
    public InnerInterceptor paginationInterceptor() {
        return new PaginationInnerInterceptor();
    }
}

性能监控拦截器

public class PerformanceMonitorInterceptor implements InnerInterceptor {
    
    private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitorInterceptor.class);
    private static final ThreadLocal<Long> startTime = new ThreadLocal<>();
    
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, 
                           RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        startTime.set(System.currentTimeMillis());
    }
    
    @Override
    public void beforeUpdate(Executor executor, MappedStatement ms, Object parameter) {
        startTime.set(System.currentTimeMillis());
    }
    
    @Override
    public void afterQuery(Executor executor, MappedStatement ms, Object parameter, 
                          RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, Object result) {
        long cost = System.currentTimeMillis() - startTime.get();
        if (cost > 1000) { // 超过1秒的记录警告日志

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-plus

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值