SpringBoot自定义MyBatis插件

自定义MyBatis插件实现原理

  • MyBatis对数据库的操作,通过其内部四个核心接口的对应实现类完成。四个核心接口如下:
    • Executor(执行器): 执行SQL的
    • StatementHandler(SQL语法构建器): 构建SQL语句的
    • ParameterHandler(参数处理器): 处理SQL语句的入参
    • ResultSetHandler(结果集处理器): 处理SQL结果的
  • 插件实现原理,就是通过动态代理,增强对应接口中方法。

自定义插件

  • 实现:给每条SELECT查询语句添加一个特定条件(1 =1)
    • 如:select * from t1 where id = 2 and 1=1

实现MyBatis插件接口Interceptor,并重写对应方法

package gk.springboot.preheat.plugin;

import org.apache.ibatis.executor.statement.BaseStatementHandler;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Properties;

/**
 * 参考文章:https://zhuanlan.zhihu.com/p/379651664(MyBatis插件修改原SQL语句)
 * MyBatis插件原理:https://juejin.cn/post/7153883569738219534?searchId=202308251201531E6F274EC915A96DA07E
 */
 // 使用@Intercepts注解完成插件签名
@Intercepts({
		// 签名:即说明插件需要拦截四大对象之一的哪一个对象的哪一个方法
		// 签名可以设置多个,这样只要执行对应方法,就会被拦截
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
})
public class TenantMyBatisPlugin implements Interceptor {

    // 每次执行StatementHandler接口中query(Statement.class, ResultHandler.class)方法时,都会对该方法进行增强
    // 增强逻辑就是通过重写intercept()方法实现
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("target对象 => " + invocation.getTarget());
        StatementHandler target = (StatementHandler) invocation.getTarget();
        Field delegateField = target.getClass().getDeclaredField("delegate");
        delegateField.setAccessible(true);
        // 获取到 StatementHandler 实现类的 delegate属性对象
        Object delegateObject = delegateField.get(target);
        Class<?> superclass = delegateObject.getClass().getSuperclass();
        // 根据delegate对象,找到其父类中mappedStatement属性对象
        Field mappedStatementField = superclass.getDeclaredField("mappedStatement");
        mappedStatementField.setAccessible(true);
        // 根据mappedStatement对象中SqlCommandType判断是否为SELECT查询
        MappedStatement mappedStatementObject = (MappedStatement) mappedStatementField.get(delegateObject);
        if (mappedStatementObject.getSqlCommandType() == SqlCommandType.SELECT) {
            BoundSql boundSql = target.getBoundSql();
            String newSql = boundSql.getSql().replace(";", "");
            if (newSql.contains("where")) {
                newSql += " and 1 = 1";
            } else {
                newSql += " where 1 = 1";
            }
            Field sqlField = boundSql.getClass().getDeclaredField("sql");
            sqlField.setAccessible(true);
            sqlField.set(boundSql, newSql);
            System.out.println("最终SQL => " + boundSql.getSql());
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        System.out.println("包装的目标对象 => " + target);
        return Plugin.wrap(target, this);
    }

    /**
     * 获取配置文件信息
     * 插件初始化时调用,即只会调用一次,插件的属性配置从这里设置进来
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("插件初始化参数 => " + properties);
    }
}

将自定义插件注入到MyBatis中去

package gk.springboot.preheat.config;

import gk.springboot.preheat.plugin.TenantMyBatisPlugin;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisConfig {

    // 将自定义插件注册到Spring的IOC就可以
    // @Bean("tenantMyBatisPlugin")
    public TenantMyBatisPlugin tenantMyBatisPlugin() {
        return new TenantMyBatisPlugin();
    }

    // 或者通过实现ConfigurationCustomizer接口,重写customize方法
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        /*return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                configuration.addInterceptor(new TenantMyBatisPlugin());
            }
        };*/
        // 上面的lambda表达式写法
        return configuration -> configuration.addInterceptor(new TenantMyBatisPlugin());
    }
}

测试

  • 测试SELECT语句是否添加了 1 =1 的where条件
    正常添加1=1条件
  • 测试UPDATE语句是否没有添加
    没有对应日志,没有添加条件

参考文章

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值