前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis plus 动态表名插件开发

Mybatis plus 动态表名插件开发

原创
作者头像
啵啵肠
发布2023-11-20 14:42:21
2250
发布2023-11-20 14:42:21
举报

Mybatis plus 动态表名插件开发

开发背景:表进行数据归档时,结构一致,但调用的时候又不想重复复制相关的代码逻辑,所以开发了个动态修改表名的插件。虽然说高版本的 mybatis plus 提供了同样的插件,但是需要升级版本。高版本的 mybatis plus 改动太大,升级的话有很大的风险,所以就自己开发了一个插件。

插件

mybatis 插件

代码语言:text
复制
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.rookie.mybatis.study.entity.DynamicTableInfo;
import com.rookie.mybatis.study.entity.DynamicTableName;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
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 org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;

/**
 * @author rookie
 */
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DynamicTableInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
//        只处理增删改查语句
        if (SqlCommandType.SELECT != mappedStatement.getSqlCommandType()
                && SqlCommandType.INSERT != mappedStatement.getSqlCommandType()
                && SqlCommandType.DELETE != mappedStatement.getSqlCommandType()
                && SqlCommandType.UPDATE != mappedStatement.getSqlCommandType()) {
            return invocation.proceed();
        }

        // 针对定义了rowBounds,做为mapper接口方法的参数
        BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
        Object paramObj = boundSql.getParameterObject();

        DynamicTableInfo info = extractInfo(paramObj);

        Map<String, String> tableMap = TablePluginContainer.TABLE_NAME_THREAD_LOCAL.get();

//        如果参数或者 ThreadLocal 中带有需要替换的表名
        boolean replaceProcess = (info == null || ObjectUtils.isEmpty(info.getTableNames())) && tableMap == null;

        if (replaceProcess) {
            return invocation.proceed();
        }

        Map<String, String> tableNameMap = null;

//        以 ThreadLocal 中的数据优先
        if (tableMap != null) {
            tableNameMap = tableMap;
        } else {
            tableNameMap = info.getTableNames().stream().collect(Collectors.toMap(DynamicTableName::getCurrentTable,
                    DynamicTableName::getItemTable, (v1, v2) -> v1));
        }

        String originalSql = boundSql.getSql();

        Statement statement = CCJSqlParserUtil.parse(originalSql);

//        替换相应表名
        originalSql = alterTableName(mappedStatement, statement, tableNameMap);

        metaObject.setValue("delegate.boundSql.sql", originalSql);
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {

    }

    private static Table alterTableName(Table table, Map<String, String> tableNameMap) {
        String tableName = tableNameMap.get(table.getName());
        if (StringUtils.isNotBlank(tableName)) {
            table.setName(tableName);
        }
        return table;
    }

    private static DynamicTableInfo extractInfo(Object paramObj) {
        // 判断参数里是否有DynamicTableInfo对象
        DynamicTableInfo info = null;
        if (paramObj instanceof DynamicTableInfo) {
            info = (DynamicTableInfo) paramObj;
        } else if (paramObj instanceof Map) {
            for (Object arg : ((Map<?, ?>) paramObj).values()) {
                if (arg instanceof DynamicTableInfo) {
                    info = (DynamicTableInfo) arg;
                    break;
                }
            }
        }
        return info;
    }

    private static String alterTableName(MappedStatement mappedStatement, Statement statement, Map<String, String> tableNameMap) {
        List<Table> tableList = new ArrayList<>();
        String originalSql = null;

        if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT) {
            Insert insertStatement = (Insert) statement;
            Table table = alterTableName(insertStatement.getTable(), tableNameMap);
            insertStatement.setTable(table);
            originalSql = insertStatement.toString();
        } else if (mappedStatement.getSqlCommandType() == SqlCommandType.DELETE) {
            Delete deleteStatement = (Delete) statement;
            Table table = alterTableName(deleteStatement.getTable(), tableNameMap);
            deleteStatement.setTable(table);
            originalSql = deleteStatement.toString();
        } else if (mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE) {
            Update updateStatement = (Update) statement;
            for (Table p : updateStatement.getTables()) {
                Table table = alterTableName(p, tableNameMap);
                tableList.add(table);
            }
            updateStatement.setTables(tableList);
            originalSql = updateStatement.toString();
        } else if (mappedStatement.getSqlCommandType() == SqlCommandType.SELECT) {
            Select selectStatement = (Select) statement;
            PlainSelect plainSelect = (PlainSelect) selectStatement.getSelectBody();
            Table fromItem = (Table) plainSelect.getFromItem();
            Table table = alterTableName(fromItem, tableNameMap);
            plainSelect.setFromItem(table);
            List<Join> joinList = new ArrayList<>();
            if (!ObjectUtils.isEmpty(plainSelect.getJoins())) {
                for (Join p : plainSelect.getJoins()) {
                    if (p.getRightItem() != null) {
                        Table tableInner = alterTableName((Table) p.getRightItem(), tableNameMap);
                        p.setRightItem(tableInner);
                        joinList.add(p);
                    }
                }
                plainSelect.setJoins(joinList);
            }

            originalSql = plainSelect.toString();
        }

        return originalSql;
    }
}

因为 mybatis plus 重写了一些类,只要将相关的插件放到 Spring 当中就会自动加载插件。所以这里注册一下 Bean 就行。

代码语言:typescript
复制
@Configuration
public class MybatisPlusConfig {

    @Bean
    public DynamicTableInterceptor dynamicTableInterceptor(){
        return new DynamicTableInterceptor();
    }
}

其他类

代码语言:swift
复制
public class TablePluginContainer {

    /**
     * 需要更改表名的缓存,请调用后手动清除
     * 当前表,需要替换的表名
     */
    public static ThreadLocal<Map<String,String>> TABLE_NAME_THREAD_LOCAL = new ThreadLocal<>();
}

@Data
public class DynamicTableInfo {

    private List<DynamicTableName> tableNames;
}


@Data
@AllArgsConstructor
public class DynamicTableName {

    /**
     * 当前表
     */
    private String currentTable;

    /**
     * 需要替换的表
     */
    private String itemTable;
}
使用方法

这里提供了两种方式注入。

第一种是像分页插件一样在 mapper 注入相应的实体类就可以了。

代码语言:text
复制
void selectTest(DynamicTableInfo dynamicTableInfo);

第二种是在逻辑代码的前后添加,考虑到 plus 是动态生成 sql,还是 threadLocal 方便点。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Mybatis plus 动态表名插件开发
    • 插件
      • 使用方法
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档