mybatis拦截器,解决shardingsphere无法查询分表的问题

项目中用到是shardingsphere版本是4.1.1,该版本不能支持 UNION ALL这种操作,但是SQL拆分太麻烦,于是通过mybatis发拦截器解决,通过自定义注解,找到分表,提前把SQL中的逻辑表更换为对应的分表。

定义自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ShardScopeAnnotation {

    /**
     * 逻辑表
     * @return
     */
    String logicTables() default "";

}

定义拦截器

@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MybatisShardingPlugin implements Interceptor {


    private final String TABLE_SPLIT_SYMBOL = "_";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (invocation.getTarget() instanceof RoutingStatementHandler){
            //1.获取原始sql语句
            RoutingStatementHandler statementHandler = (RoutingStatementHandler)invocation.getTarget();
            StatementHandler delegate = (StatementHandler)ReflectHelper.getFieldValue(statementHandler, "delegate");

            //2.根据方法名反射,判断是否包含@NoNeedOffice注解,来决定是否需要修改sql语句
            MappedStatement mappedStatement = (MappedStatement) ReflectHelper.getFieldValue(delegate, "mappedStatement");
            String sqlId = mappedStatement.getId();

            BoundSql boundSql = delegate.getBoundSql();
            // 获取传入的参数
            Object parameterMappings = statementHandler.getBoundSql().getParameterObject();

            ShardingBaseEntity entity = getShardingParam(parameterMappings);

            if (hasShardScopeAnnotation(sqlId) && entity != null && entity.getDay() != null){
                modifySql(boundSql, mappedStatement, entity);
            }
        }
        return invocation.proceed();
    }


    private void modifySql(BoundSql boundSql, MappedStatement mappedStatement, ShardingBaseEntity baseEntity){
        ShardScopeAnnotation annotation = getFieldAnnotation(mappedStatement);
        String logicTables = annotation.logicTables();
        String sql = boundSql.getSql();

        String[] logicTableArr = logicTables.split(",");
        if (logicTableArr.length == 0){
            return;
        }

        for (String actualTable : logicTableArr) {
            actualTable = actualTable.trim().toLowerCase();

            ShardingTableCacheEnum logicTable = ShardingTableCacheEnum.of(actualTable);
            String resultTableName = actualTable +TABLE_SPLIT_SYMBOL+ ShardingUtil.getYearMonth(baseEntity.getAttendanceDay());
            if (logicTable.resultTableNamesCache().contains(resultTableName)){
                sql = sql.replaceAll("\\b"+actualTable+"\\b", resultTableName);
            }
        }
        
        ReflectHelper.setFieldValue(boundSql, "sql", sql);
    }


    private ShardingBaseEntity getShardingParam(Object paramObject){
        if (paramObject == null){
            return null;
        }
        ShardingBaseEntity shardingBaseEntity = null;
        if (paramObject instanceof Map){
            Map<String, Object> params = (Map<String, Object>) paramObject;
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                if (entry.getValue() instanceof ShardingBaseEntity) {
                    return (ShardingBaseEntity) entry.getValue();
                }
            }
        }else if (paramObject instanceof ShardingBaseEntity){

            shardingBaseEntity = (ShardingBaseEntity) paramObject;
        }
        return shardingBaseEntity;
    }

    /**
     * 判断注解中是否包含 自定义的注解
     * @param classAnnotations
     * @return
     */
    private boolean containsShardScopeAnnotation(Annotation[] classAnnotations) {
        for (Annotation annotation : classAnnotations) {
            if (annotation instanceof ShardScopeAnnotation) {
                return true;
            }
        }
        return false;
    }

    private boolean hasShardScopeAnnotation(String sqlId){
        //1.得到类路径和方法路径
        int lastIndexOfDot = sqlId.lastIndexOf(".");
        String className = sqlId.substring(0, lastIndexOfDot);
        String methodName = sqlId.substring(lastIndexOfDot + 1);

        //2.得到类上的注解
        Class<?> clazz = null;
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        Annotation[] classAnnotations = clazz.getAnnotations();

        if (containsShardScopeAnnotation(classAnnotations)) {
            return true;
        }

        //3.得到方法上的注解
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            String name = method.getName();
            if (methodName.equals(name)) {
                Annotation[] methodAnnotations = method.getAnnotations();
                if (containsShardScopeAnnotation(methodAnnotations)) {
                    return true;
                }
            }
        }
        return false;
    }

    private ShardScopeAnnotation getFieldAnnotation(MappedStatement mappedStatement) {
        ShardScopeAnnotation annotation = null;
        try {
            String id = mappedStatement.getId();
            String className = id.substring(0, id.lastIndexOf("."));
            String methodName = id.substring(id.lastIndexOf(".") + 1);
            final Method[] method = Class.forName(className).getMethods();
            for (Method me : method) {
                if (me.getName().equals(methodName) && me.isAnnotationPresent(ShardScopeAnnotation.class)) {
                    return me.getAnnotation(ShardScopeAnnotation.class);
                }
            }
        } catch (Exception ex) {
            System.out.println(ex);
        }
        return annotation;
    }

}

在dao上

@ShardScopeAnnotation(logicTables = "table1, table2")
List<User> findList2(UserBO UserBO );

这样在代码执行到拦截器的时候,根据传入的分表参数,动态的找到准确的分表,然后把SQL中的表给替换掉,shardingspere在执行的时候,会将原SQL给执行,然后输出结果。

注意,这里是单个分表的查询,不执行多个分表的查询,如果涉及到多个分表,那么需要循环执行,然后将结果合并

Mybatis-Plus 是一个 Mybatis 的增强工具,在 Mybatis 的基础上进行了功能扩展和常用 CRUD 操作的封装,极大地简化了开发流程。而 InnerInterceptor 则是 Mybatis-Plus 中的一个拦截器接口,可用于实现分表等高级功能。 要实现分表,我们可以通过实现 InnerInterceptor 接口来拦截 SQL 语句并根据需要修改表名。以下是一个简单的示例: ```java public class MyInnerInterceptor implements InnerInterceptor { @Override public void beforePrepare(StatementHandler sh, Connection conn, Integer transactionTimeout) { BoundSql boundSql = sh.getBoundSql(); String sql = boundSql.getSql(); if (sql.startsWith("select")) { // 根据某个条件判断是否需要分表 if (needSharding()) { // 修改表名为实际表名 sql = sql.replace("my_table", "my_table_1"); ReflectUtil.setFieldValue(boundSql, "sql", sql); } } } } ``` 在上面的示例中,我们实现了一个内部拦截器 MyInnerInterceptor,并在其中实现了 beforePrepare 方法。该方法会在执行 SQL 语句之前被拦截,并根据需要修改 SQL 语句中的表名。在这里,我们通过判断是否需要分表来决定是否修改表名,如果需要分表则将表名修改为实际表名。 最后,将 MyInnerInterceptor 注册到 Mybatis-Plus 的拦截器链中即可: ```java @Configuration public class MybatisPlusConfig { @Bean public MyInnerInterceptor myInnerInterceptor() { return new MyInnerInterceptor(); } @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(MyInnerInterceptor myInnerInterceptor) { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(myInnerInterceptor); return interceptor; } } ``` 通过以上步骤,我们就可以使用 InnerInterceptor 来实现分表等高级功能了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值