自定义拦截器修改sql
问题:系统需支持多租户使用,一个租户对应一个schema,增删改查的sql语句中需根据用户所属租户找到对应的schema,修改拼接sql,然后再执行sql语句
需实现效果:
执行前sql:
select * from student_test where id = 1
拦截修改后sql:
select * from s1.student_test where id = 1
实现代码:
@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class TestInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性;:MetaObject是Mybatis提供的一个用于方便、
//优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
MetaObject metaObject = MetaObject.forObject(statementHandler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
//先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
BoundSql boundSql = statementHandler.getBoundSql();
Field sql = boundSql.getClass().getDeclaredField("sql");
sql.setAccessible(true);
//sql替换 根据sql中的表名与数据库表名匹配替换 test_table->s1.test_table
// boundSql.getSql() ->获取待执行sql
String replace = replace(boundSql.getSql(), mappedStatement);
//替换待执行sql
sql.set(boundSql, replace);
return invocation.proceed();
}
注意点:
- 拦截器的拦截类需为StatementHandler, 如果拦截类为Executor,则修改数据操作修改类型的sql(update delete insert)不生效
- BoundSql中的sql为私有属性,需设置sql.setAccessible(true);
结合mybatis分页插件使用
问题:在分页查询中使用分页插件进行分页处理,在引入多租户多schema概念后,分页插件自动创建的分页查询无法查询出结果,控制台报stackoverflow错误
原因排查:debugger发现自定义拦截器存在sql查询,执行sql之后又再次进入sql分页拦截器,sql分页拦截器->自定义拦截器查询->分页拦截器
思路:取消sql查询,改为预先放入redis,再从redis中取出