MyBatis拦截器修改sql表名

MyBatis拦截器修改待执行sql表名

项目中,有时候为了防止表数据过大,要对表中的一部分用不到的历史数据迁移出去(迁移到另外一个表),但是随着业务需要,偶尔需要查询被迁移出去的历史数据。在此通过Mybatis拦截器、自定义注解、获取参数来修改表名

一、自定义注解

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TableNameChange {

    /**
     * 替换前的表名
     */
    String[] originalTableNames();

    /**
     * 替换后对表名
     */
    String[] modifiedTableNames();

}

二、实现Mybatis的Interceptor接口

@Intercepts({@Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
					), @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, 					BoundSql.class})})
@Slf4j
@Component
public class TableNameChangeInterceptor implements Interceptor {
			//使用该方法在拦截中途添加自己想要的自定义操作。
	  Object intercept(Invocation var1) throws Throwable;
  
			//将插件添加到mybatis操作中。
    default Object plugin(Object target) {
      //其中这里就是将拦截器和对象包装在一起:详细见下面
        return Plugin.wrap(target, this);
    }
		//读取配置中的属性。
    default void setProperties(Properties properties) {}
}
2.1 添加@Intercepts注解

Intercepts是一个Signature类型的数组,通过该注解去判断当前方法是否需要被拦截。

@Signature注解的三个参数

  1. type 指定拦截的类的类型,type有4个类型可选

    // Mybatis中所有的Mapper语句的执行都是通过Executor进行的。Executor是Mybatis的核心接口。从其定义的接口方法我们可以看出,对应的增删改语句是通过Executor接口的update方法进行的,查询是通过query方法进行的。
    Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed)
    //ParameterHandler用来设置参数规则,当StatementHandler使用prepare()方法后,接下来就是使用它来设置参数。所以如果有对参数做自定义逻辑处理的时候,可以通过拦截ParameterHandler来实现
    ParameterHandler (getParameterObject, setParameters)
    // ResultSetHandler用于对查询到的结果做处理。所以如果你有需求需要对返回结果做特殊处理的情况下可以去拦截ResultSetHandler的处理。
    ResultSetHandler (handleResultSets,handleOutputParameters)
    //封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
    StatementHandler (prepare, parameterize,batch, update, query)
    
  2. method 指定拦截的方法名

  3. args 指定方法的参数类型,去对应的方法里看有哪些参数类型就可以了。如果填错会报错。

2.2 重写intercept(Invocation var1) 方法
@Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            Object[] args = invocation.getArgs();
            MappedStatement ms = (MappedStatement) args[0];
            Object parameter = args[1];//传递的参数
            TableNameChange annotation = getTableNameChange(ms);//获取注解方法
            if (ObjectUtil.isNotEmpty(annotation)) {
                String[] originalTableNames = annotation.originalTableNames();
                String[] modifiedTableNames = annotation.modifiedTableNames();
                if (ObjectUtil.notEqual(originalTableNames.length, modifiedTableNames.length)) {
                    log.error("The property of TableNameChange is incorrectly set ");
                    return invocation.proceed();
                }
              //通过传递的changeType判断是否需要替换表名
                String changeType = getParamByName(parameter, "changeType");
                if (StrUtil.isNotEmpty(changeType) && StrUtil.equals(changeType, TableNameChangeType.CHANGE.name())) {
                    BoundSql boundSql;
                    if (args.length == 4) {
                        boundSql = ms.getBoundSql(parameter);
                    } else {
                        boundSql = (BoundSql) args[5];
                    }
                    // 获取执行的SQL
                    String sql = boundSql.getSql();
                    // 获取执行的参数
                    // 判断是否存在需要替换的表,不存在放行
                    String s = sql;
                    for (int i = 0; i < originalTableNames.length; i++) {
                        if (sql.contains(originalTableNames[i])) {
                            s = sql.replaceAll(originalTableNames[i], modifiedTableNames[i]);
                        }
                        // 替换执行的的SQL
                    }
                    ReflectUtil.setFieldValue(boundSql, "sql", s);
                }
            }
        } catch (Exception ex) {
            log.error(ex.getMessage());
        }
        return invocation.proceed();
    }
2.2.1获取注解方法
private TableNameChange getTableNameChange(MappedStatement mappedStatement) {
    TableNameChange annotation = null;
    try {
        String id = mappedStatement.getId();
        String className = id.substring(0, id.lastIndexOf("."));
        String methodName = id.substring(id.lastIndexOf(".") + 1);
        if (methodName.contains("WithChangeTableName")) {//限制方法名称
            final Method[] method = Class.forName(className).getMethods();
            for (Method me : method) {
                if (me.getName().equals(methodName) && me.isAnnotationPresent(TableNameChange.class)) {
                    annotation = me.getAnnotation(TableNameChange.class);
                }
            }
        }
    } catch (Exception ex) {
        log.error(ex.getMessage());
    }
    return annotation;
}
2.2.2 根据属性名获取属性
private String getParamByName(Object parameter, String paramName) {
    String value = null;
    if (parameter instanceof Map) {
        Map param = (Map) parameter;
        if (!param.containsKey(paramName)) {
            int size = param.keySet().size() / 2;
            for (int i = 1; i <= size; i++) {
                String key = "param" + i;
                Object o = param.get(key);
                if (o instanceof QueryWrapper) {
                    QueryWrapper q = (QueryWrapper) o;
                    String sqlSegment = q.getExpression().getSqlSegment();
                    String s = paramName + " =";
                    if (sqlSegment.contains(s)) {
                        String pKey = sqlSegment.split(s)[1].split("paramNameValuePairs.")[1].split("}")[0];
                        value = q.getParamNameValuePairs().get(pKey).toString();
                        break;
                    }
                }
            }
        } else {
            value = param.get(paramName).toString();
        }
    } else if (parameter instanceof String) {
        value = parameter.toString();
    } else {
        JSONObject json = (JSONObject) JSON.toJSON(parameter);
        value = json.get(paramName).toString();
    }
    return value;
}

三、小结

如何实现在一个表中查询结果为null,自动更换表名去另外一个表中查询

//先执行一下invocation.proceed(),判断为空,再修改表名执行一次
				Object proceed = invocation.proceed();
        if (ObjectUtil.isNotEmpty(proceed)) {
            return proceed;
        }
        // 判断是否存在需要替换的表,不存在放行
        if (sql.contains(annotation.originalTableName())) {
            String s = sql.replaceAll(annotation.originalTableName(), annotation.modifiedTableName());
            // 替换执行的的SQL	
            ReflectUtil.setFieldValue(boundSql, "sql", s);
        } else {
            return proceed;
        }
    }
}
return invocation.proceed();

此时需要执行两次invocation.proceed();最终反射调用的native本地方法传参如下图,虽然参数变了,但是并没有打印第二个sql语句,所以还是没有结果,我想可能是因为默认开启了mybatis的一级缓存,在一个会话中有效,导致第二个sql没有执行。而事实确实如此!!!

image-20230324150839903

关闭一级缓存

mybatis:
  configuration:
    cache-enabled: false  #禁用二级缓存
    local-cache-scope: statement  #禁用了一级缓存
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值