0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Mybatis 拦截器实现单数据源内多数据库切换

京东云 来源:京东保险 王奕龙 作者:京东保险 王奕龙 2024-12-12 10:23 次阅读

作者:京东保险 王奕龙

物流的分拣业务在某些分拣场地只有一个数据源,因为数据量比较大,将所有数据存在一张表内查询速度慢,也为了做不同设备数据的分库管理,便在这个数据源内创建了多个不同库名但表完全相同的数据库

现在需要上线报表服务来查询所有数据库中的数据进行统计,那么现在的问题来了,该如何 满足在配置一个数据源的情况下来查询该数据源下不同数据库的数据 呢,借助搜索引擎查到的分库实现大多是借助 Sharding-JDBC 框架,配置多个数据源根据分库算法实现数据源的切换,但是对于只有一个数据源的系统来说,我觉得引入框架再将单个数据源根据不同的库名配置成多个不同的数据源来实现分库查询的逻辑我觉得并不好。

如果我们能在 SQL 执行前将 SQL 中所有的表名前拼接上对应的库名的话,那么就能够实现数据源的切换了,下面我们讲一下使用 JSqlParser 和 Mybatis拦截器 实现该逻辑,借助 JSqlParser 主要是为了解析SQL,找到其中所有的表名进行拼接,如果大家有更好的实现方式,该组件并不是必须的。

实现逻辑

SqlSource 是读取 XML 中 SQL 内容并将其发送给数据库执行的对象,如果我们在执行前能拦截到该对象,并将其中的 SQL 替换掉便达成了我们的目的。 SqlSource 有多种实现,包括常见的DynamicSqlSource。其中包含着必要的执行逻辑,我们需要做的工作便是在这些逻辑执行完之后,对 SQL 进行改造,所以这次实现我们使用了 装饰器模式,在原来的 SqlSource 上套一层,执行完 SqlSource 本身的方法之后对其进行增强,代码如下:

public abstract class AbstractDBNameInterceptor {

    /**
     * SqlSource 的装饰器,作用是增强了 getBoundSql 方法,在基础上增加了动态分库的逻辑
     */
    static class SqlSourceDecorator implements SqlSource {

        /**
         * SQL 字段名称
         */
        private static final String SQL_FIELD_NAME = "sql";

        /**
         * 原本的 sql source
         */
        private final SqlSource sqlSource;

        /**
         * 装饰器进行封装
         */
        public SqlSourceDecorator(SqlSource sqlSource) {
            this.sqlSource = sqlSource;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            try {
                // 先生成出未修改前的 SQL
                BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
                // 获取数据库名
                String dbName = getSpecificDBName(parameterObject);
                // 有效才修改
                if (isValid(dbName)) {
                    // 生成需要修改完库名的 SQL
                    String targetSQL = getRequiredSqlWithSpecificDBName(boundSql, dbName);
                    // 更新 SQL
                    updateSql(boundSql, targetSQL);
                }

                return boundSql;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        /**
         * 校验是否为有效库名
         */
        private boolean isValid(String dbName) {
            return StringUtils.isNotEmpty(dbName) && !"null".equals(dbName);
        }

        /**
         * 获取到我们想要的库名的 SQL
         */
        private String getRequiredSqlWithSpecificDBName(BoundSql boundSql, String dbName) throws JSQLParserException {
            String originSql = boundSql.getSql();
            // 获取所有的表名
            Set< String > tables = TablesNamesFinder.findTables(originSql);
            for (String table : tables) {
                originSql = originSql.replaceAll(table, dbName + "." + table);
            }
            return originSql;
        }

        /**
         * 修改 SQL
         */
        private void updateSql(BoundSql boundSql, String sql) throws NoSuchFieldException, IllegalAccessException {
            // 通过反射修改sql语句
            Field field = boundSql.getClass().getDeclaredField(SQL_FIELD_NAME);
            field.setAccessible(true);
            field.set(boundSql, sql);
        }
    }
    
    // ... 
}

定义了 AbstractDBNameInterceptor 抽象类是为了实现复用,并将 SqlSourceDecorator 装饰器定义为静态内部类,这样的话,将所有逻辑都封装在抽象类内部,之后这部分实现好后研发直接实现抽象类的通用方法即可,不必关注它的内部实现。

结合注释我们解释一下 SqlSourceDecorator 的逻辑,其中用到了 Java 反射相关的操作。首先通过反射获取到 SQL,getSpecificDBName 方法是需要自定义实现的,其中 parameterObject 对象是传到 DAO 层执行查询时的参数,在我们的业务中是能够根据其中的设备相关参数拿到对应的所在库名的,而设备和具体库名的映射关系需要提前初始化好。在获取到具体的库名后执行 getRequiredSqlWithSpecificDBName 方法来将其拼接到表名前,在这里我们使用到了 JSqlParser 的工具类,解析出来所有的表名,执行字符串的替换,最后一步同样是使用反射操作将该参数值再写回去,这样便完成了指定库名的任务。

接下来我们需要看下抽象拦截器中供拦截器复用的方法,如下:

public abstract class AbstractDBNameInterceptor {

    /**
     * SqlSource 字段名称
     */
    private static final String SQL_SOURCE_FIELD_NAME = "sqlSource";

    /**
     * 执行修改数据库名的逻辑
     */
    protected Object updateDBName(Invocation invocation) throws Throwable {
        // 装饰器装饰 SqlSource
        decorateSqlSource((MappedStatement) invocation.getArgs()[0]);
        return invocation.proceed();
    }

    /**
     * 装饰 SqlSource
     */
    private void decorateSqlSource(MappedStatement statement) throws NoSuchFieldException, IllegalAccessException {
        if (!(statement.getSqlSource() instanceof SqlSourceDecorator)) {
            Field sqlSource = statement.getClass().getDeclaredField(SQL_SOURCE_FIELD_NAME);
            sqlSource.setAccessible(true);
            sqlSource.set(statement, new SqlSourceDecorator(statement.getSqlSource()));
        }
    }
}

这个还是比较简单的,只是借助反射机制做了一层“装饰”,查询拦截器实现如下:

@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})
})
public class SelectDBNameInterceptor extends AbstractDBNameInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return updateDBName(invocation);
    }
}

将其配置到 Mybatis 拦截器中,便能实现数据库动态切换了。

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据库
    +关注

    关注

    7

    文章

    3782

    浏览量

    64333
  • mybatis
    +关注

    关注

    0

    文章

    60

    浏览量

    6706
收藏 人收藏

    评论

    相关推荐

    lanbview怎么与数据库连接?

    择SQL Server驱动程序,新建一个SQL Server的驱动程序②在“创建SQL Server创建数据源”窗口中输入“Mydb”作为连接的DSN的名称,同时根据远程数据库服务的配置,在“服务
    发表于 03-22 11:32

    labvies访问数据库

    )是微软最新的数据库访问技术,可以用于编写通过OLE DB 提供者对在数据库服务中的数据进行访问和操作的应用程序。OLE DB 是一个底层的数据
    发表于 08-26 10:19

    LabView动态创建数据源的方法

    DSN(Data Source Name,数据源名)。LabSQL与数据库之间的连接就是建立在DSN 基础之上的。但是这种过于麻烦,在生成操作程序时不便于安装,于是需要一种可以在LabView中直接创建数据源的方法。通过资料查证
    发表于 09-23 01:53

    LabVIEW连接Access数据库的问题

    想在LabVIEW中用Access数据库实现数据操作,但是电脑没法连上数据源,照着网站上的做法没有找到数据源,具体情况如图,最后报错。
    发表于 11-05 21:45

    QuickBI助你成为分析师——搞定数据源

    分析师”。产品的核心流程如下图所示,QuickBI实现无缝集成云上数据库:支持阿里云多种数据源,包括但不限于 MaxCompute、RDS(MySQL、PostgreSQL、SQL Server
    发表于 03-28 12:43

    springboo修改数据源为Druid

    springboo修改数据源Druid整合mybatis 使用Mybatis-Generator插件生成代码和分页插件
    发表于 05-05 14:45

    mybatis支持数据库轻兼容的轻量方案

    一个轻量的方案, 令mybatis支持数据库轻兼容
    发表于 04-09 17:44

    SpringBoot项目多数据源配置数据库

    SpringBoot项目多数据源配置
    发表于 06-05 09:51

    数据源配置工具

    odbc_for_access.exe1、可直接配置数据源。2、可直接创建Access数据库文件。3、为数据库配置密码。
    发表于 07-01 16:22 4次下载

    springmvc 自定义拦截器实现未登录用户的拦截

    springmvc自定义拦截器实现未登录用户的拦截
    发表于 11-25 14:44 2519次阅读
    springmvc 自定义<b class='flag-5'>拦截器</b><b class='flag-5'>实现</b>未登录用户的<b class='flag-5'>拦截</b>

    数据仓库入门之创建数据源

    首先需要创建一个数据源,SSAS(分析服务)将利用数据源来连接数据库。一、准备环境二、启动SSDT,新建项目三、创建数据源
    发表于 02-24 14:48 2528次阅读
    <b class='flag-5'>数据</b>仓库入门之创建<b class='flag-5'>数据源</b>

    基于Mybatis拦截器实现数据范围权限

    前端的菜单和按钮权限都可以通过配置来实现,但很多时候,后台查询数据库数据的权限需要通过手动添加SQL来实现
    的头像 发表于 06-20 09:57 1308次阅读
    基于<b class='flag-5'>Mybatis</b><b class='flag-5'>拦截器</b><b class='flag-5'>实现</b><b class='flag-5'>数据</b>范围权限

    如何实现基于Mybatis拦截器实现数据范围权限呢?

    前端的菜单和按钮权限都可以通过配置来实现,但很多时候,后台查询数据库数据的权限需要通过手动添加SQL来实现
    的头像 发表于 06-20 09:59 1216次阅读
    如何<b class='flag-5'>实现</b>基于<b class='flag-5'>Mybatis</b><b class='flag-5'>拦截器</b><b class='flag-5'>实现</b><b class='flag-5'>数据</b>范围权限呢?

    多数据源数据转换和同步的ETL工具推荐

    多种数据源的连接,包括文件系统、数据库、消息队列、网络接口等。它提供了可视化的界面和强大的数据处理功能,可以轻松地创建数据流,进行数据转换和
    的头像 发表于 07-28 16:32 1077次阅读

    SpringBoot实现动态切换数据源

    最近在做业务需求时,需要从不同的数据库中获取数据然后写入到当前数据库中,因此涉及到切换数据源问题。本来想着使用
    的头像 发表于 12-08 10:53 997次阅读
    SpringBoot<b class='flag-5'>实现</b>动态<b class='flag-5'>切换</b><b class='flag-5'>数据源</b>