MyBatis拦截器动态替换表名的方法详解

网友投稿 3104 2022-10-10

MyBatis-动态替换表名的方法详解

MyBatis-动态替换表名的方法详解

目录写在前面一、Mybatis Interceptor -接口和注解二、实现思路三、代码实现四、运行结果写在最后参考资料

写在前面

今天收到一个需求,根据请求方的不同,动态的切换表名(涵盖SELECT,INSERT,UPDATE操作)。几张新表和旧表的结构完全一致,但是分开维护。看到需求第一反应是将表名提出来当${tableName}参数,然后AOP拦截判断再替换表名。但是后面看了一下这几张表在很多mapper接口都有使用,其中还有一些复杂的连接查询,提取tableName当参数肯定是不现实的了。后面和组内大佬讨论之后,发现可以使用MyBatis提供的-,判断并且动态的替换表名。

一、Mybatis Interceptor -接口和注解

简单的说就是mybatis在执行sql的时候,拦截目标方法并且在前后加上我们的业务逻辑。实际上就是加@Intercepts注解和实现org.apache.ibatis.plugin.Interceptor接口

@Intercepts(

@Signature(method = "query",

type = Executor.class,

args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}

)

)

public interface Interceptor {

//主要重写这个方法、实现我们的业务逻辑

Object intercept(Invocation invocation) throws Throwable;

//生成代理对象,可以在这里判断是否生成代理对象

Object plugin(Object target);

//如果我们-需要用到一些变量参数,可以在这里读取

void setProperties(Properties properties);

}

二、实现思路

在intercept方法中有参数Invocation对象,里面有3个成员变量和@Signature对应

成员变量变量类型说明targetObject代理对象methodMethod被拦截方法argsObject[]被拦截方法执行所需的参数

通过Invocation中的args变量。我们能拿到MappedStatement这个对象(args[0]),传入sql语句的参数Object(args[1])。而MappedStatement是一个记录了sql语句(sqlSource对象)、参数值结构、返回值结构、mapper配置等的一个对象。sqlSource对象和传入sql语句的参数对象Object就能获得BoundSql。BoundSql的toString方法就能获取到有占位符的sql语句了,我们的业务逻辑就能在这里介入。获取到sql语句,根据规则替换表名,塞回BoundSql对象中、再把BoundSql对象塞回MappedStatement对象中。最后再赋值给args[0](实际被拦截方法所需的参数)就搞定了

三、代码实现

import org.apache.ibatis.executor.Executor;

import org.apache.ibatis.mapping.BoundSql;

import org.apache.ibatis.mapping.MappedStatement;

import org.apache.ibatis.mapping.SqlSource;

import org.apache.ibatis.plugin.*;

import org.apache.ibatis.session.ResultHandler;

import org.apache.ibatis.session.RowBounds;

import java.util.*;

/**

* @description: 动态替换表名-

* @author: hinotoyk

* @created: 2022/04/19

*/

//method = "query"拦截select方法、而method = "update"则能拦截insert、update、delete的方法

@Intercepts({

@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),

@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})

})

public class ReplaceTableInterceptor implements Interceptor {

private final static Map TABLE_MAP = newVnvAOBYLnL LinkedHashMap<>();

static {

//表名长的放前面,避免字符串匹配的时候先匹配替换子集

TABLE_MAP.put("t_game_partners","t_game_partners_test");//测试

TABLE_MAP.put("t_file_recycle","t_file_recycle_other");

TABLE_MAP.put("t_folder","t_folder_other");

TABLE_MAP.put("t_file","t_file_other");

}

@Override

public Object intercept(Invocation invocation) throws Throwable {

Object[] args = invocation.getArgs();

//获取MappedStatement对象

MappedStatement ms = (MappedStatement) args[0];

//获取传入sql语句的参数对象

Object parameterObject = args[1];

BoundSql boundSql = ms.getBoundSql(parameterObject);

//获取到拥有占位符的sql语句

String sql = boundSql.getSql();

System.out.println("拦截前sql :" + sql);

//判断是否需要替换表名

if(isReplaceTableName(sql)){

for(Map.Entry entry : TABLE_MAP.entrySet()){

sql = sql.replace(entry.getKey(),entry.getValue());

}

System.out.println("拦截后sql :" + sql);

//重新生成一个BoundSql对象

BoundSql bs = new BoundSql(ms.getConfiguration(),sql,boundSql.getParameterMappings(),parameterObject);

//重新生成一个MappedStatement对象

MappedStatement newMs = copyMappedStatement(ms, new BoundSqlSqlSource(bs));

//赋回给实际执行方法所需的参数中

args[0] = newMs;

}

return invocation.proceed();

}

@Override

public Object plugin(Object target) {

return Plugin.wrap(target, this);

}

@Override

public void setProperties(Properties properties) {

}

/***

* 判断是否需要替换表名

* @param sql

* @return

*/

private boolean isReplaceTableName(String sql){

for(String tableName : TABLE_MAP.keySet()){

if(sql.contains(tableName)){

return true;

}

}

return false;

}

/***

* 复制一个新的MappedStatement

* @param ms

* @param newSqlSource

* @return

*/

private MappedStatement copyMappedStatement (MappedStatement ms, SqlSource newSqlSource) {

MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());

builder.resource(ms.getResource());

builder.fetchSize(ms.getFetchSize());

builder.statementType(ms.getStatementType());

builder.keyGenerator(ms.getKeyGenerator());

if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {

builder.keyProperty(String.join(",",ms.getKeyProperties()));

}

builder.timeout(ms.getTimeout());

builder.parameterMap(ms.getParameterMap());

builder.resultMaps(ms.getResultMaps());

builder.resultSetType(ms.getResultSetType());

builder.cache(ms.getCache());

builder.flushCacheRequired(ms.isFlushCacheRequired());

builder.useCache(ms.isUseCache());

return builder.build();

}

/***

* MappedStatement构造器接受的是SqlSource

* 实现SqlSource接口,将BoundSql封装进去

*/

public static class BoundSqlSqlSource implements SqlSource {

private BoundSql boundSql;

public BoundSqlSqlSource(BoundSql boundSql) {

this.boundSql = boundSql;

}

@Override

public BoundSql getBoundSql(Object parameterObject) {

return boundSql;

}

}

}

四、运行结果

写在最后

一开始接到这个需求的时候,会习惯性的从熟悉常用的技术入手。如果涉及的表引用没这么多,是不是就会直接用AOP拦截判断替换了呢,我大概率是会的。可能就不会想到上面的-动态替换的方法(相当于失去一次学习的机会),还是要跳出惯性多思考还有没有更合适的做法,把每次需求都当成一次学习的机会,舒适圈都能变开阔很多,共勉。

参考资料

MyBatis官网

mybatis插件实现自定义改写表名

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:国足晋级12强 | 爬取《NBA30支球队》“现役球员信息”,再来看看篮球吧!
下一篇:Pandas.set_option()函数的5种技巧,学完惊呆了!
相关文章

 发表评论

暂时没有评论,来抢沙发吧~