自定义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;
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
})
public class TenantMyBatisPlugin implements Interceptor {
@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);
Object delegateObject = delegateField.get(target);
Class<?> superclass = delegateObject.getClass().getSuperclass();
Field mappedStatementField = superclass.getDeclaredField("mappedStatement");
mappedStatementField.setAccessible(true);
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 {
public TenantMyBatisPlugin tenantMyBatisPlugin() {
return new TenantMyBatisPlugin();
}
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.addInterceptor(new TenantMyBatisPlugin());
}
}
测试
- 测试SELECT语句是否添加了 1 =1 的where条件

- 测试UPDATE语句是否没有添加

参考文章