Mybatis动态数据源及其原理

news2024/10/5 23:52:10

一、引言

        作者最近的平台项目需要一个功能,数据库是动态的,sql也是动态的,所以需要动态注入数据源,并且能够在运行过程中进行切换数据库。作者在这里分享一下做法,以及Mybatis这样做的原理。

二、分析

        接下来分析一下需求,主要是两个点:

        1、动态数据源

        既然是动态的,那肯定需要在配置中心放数据库。问题是把这堆数据库初始化起来,这个比较大众的做法就是放在多个路径下,每个路径一个数据库一个配置类,这样扫描的类就始终属于某个数据库,清晰解耦,但是这样加数据库就需要发布,而且每加一个库都要加一堆路径,正常情况下是适合的。

        但是作者的平台大部分数据库执行的操作都是一样的,这种方式带来的后续改动和发布是没有必要的,所以要把数据源给放在集合里面,只需要改配置就可以加库。

        2、运行切换
       要是一个路径一个库,没有切换的必要。数据源存在集合里面,就肯定需要切换了,切换正常都是基于aop或者拦截器。这里Mybatis提供了一些工具,基于aop实现更快。

三、实现

        先试试简单的,要有个保底,免得第二种踩坑太多时间来不及。

1、路径-库

        配置类,这里创建数据源的时候是作者公司的框架做的,正常创建是要在yml里面指定链接、账号、密码

@EnableDalMybatis(encryptParameters = false)
@Configuration
@MapperScan(basePackages = {"com.mapper"},
        sqlSessionFactoryRef = "sqlSessionFactory")
public class OneDBConfig {

    @Bean
    public DataSource dataSource() throws Exception {
        return factory.getOrCreateDataSource(DB_KEY);
    }

    @Bean(name = "transactionManager")
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean
                .setMapperLocations(resolver.getResources(
                        "classpath*:/com/*.xml"));
        sqlSessionFactoryBean.setConfigLocation(resolver.getResource("classpath:/mybatis/mybatis-config.xml"));
        return sqlSessionFactoryBean.getObject();
    }

}

        多少个库就有多少个配置类,这里有个坑在,每个配置类对应的SqlSessionFactory要起别名,不能一样。

        SqlSessionFactory用来创建SqlSession的工厂类,SqlSession是MyBatis中用于执行SQL语句的主要对象。数据源塞在这个对象里面,所以数据源和数据源管理器不起名没关系,但是他在外层,得不同。

        显然xml和mapper也要有多个对应的目录。

        用的时候就是根据db使用不同路径下的mapper

DbEnum dbEnum = DbEnum.getExecuteEnumByValue(db);
        switch (dbEnum) {
            case ONE:
                //
            case TWO:
                //
            default:
                return null;
        }

2、动态多数据源 

        继承mybatis的AbstractRoutingDataSource,这个类里面有个方法,是mybatis每次创建链接的时候使用的,根据key找对应的数据源

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceThreadLocal.getDB();
    }
}

        要使用ThreadLocal存储需要使用的数据源,这样才不会影响其他线程操作数据 

public class DataSourceThreadLocal {
    public static final String DEFAULT_DB = "default";
    private static final ThreadLocal<String> nowDb = new ThreadLocal<>();
    public static void setDB(String dbType) {
        nowDb.set(dbType);
    }

    public static String getDB() {
        // 如果当前线程没有设置切换数据库,就使用默认数据库
        if (nowDb == null || StringUtilsExt.isBlank(nowDb.get())) {
            return DEFAULT_DB;
        }
        return (nowDb.get());
    }

    public static void clearDB() {
        nowDb.remove();
    }
}

        mybatis提供了一个DynamicDataSource,里面的targetDataSources是一个hashmap,可以存放db和数据初始化的datasource,配置中心放的数据库主要是放了db的key,拿到程序里面反序列化为对象,配置中心里面放json、map、list都行。

@EnableDalMybatis(encryptParameters = false)
@Configuration
@MapperScan(
        basePackages = {
            "com.mapper"},
        sqlSessionFactoryRef = "sqlSessionFactory")
public class CommonQueryDBConfig {

    private static final LoggerService LOG = LoggerServiceFactory.getLoggerService(CommonQueryDBConfig.class);

    private static final String LOG_TITLE = "CommonQueryDBConfig";

    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() throws Exception {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        LOG.info(LOG_TITLE, "init data source");
        // 数据源可以存放在map里面
        Map<Object, Object> dbMap = new HashMap<>();
        String s = ConfigurationFunc.getString(QConfigConstants.CREATE_DATA_CONFIG_PARAMETER);
        List<DynamicDataSourceConfigBo> dataSourceConfigBos =
                JSONUtil.parse(s, new TypeReference<List<DynamicDataSourceConfigBo>>() {});
        LOG.info(LOG_TITLE, "dataSourceConfigBos:{}", JSONUtil.toJsonNoException(dataSourceConfigBos));
        for (DynamicDataSourceConfigBo ds : dataSourceConfigBos) {
            DataSource now = factory.createDataSource(ds.getDbCreateKey());
            dbMap.put(ds.getDbCreateKey(), now);
        }
        // 默认数据源        
        dynamicDataSource.setDefaultTargetDataSource(dsMap.get("corpcodescannerdb_dalcluster"));
        dynamicDataSource.setTargetDataSources(dsMap);
        LOG.info(LOG_TITLE, "init data source success:{}", dsMap.size());
        return dynamicDataSource;
    }

    @Bean(name = "dynamicTransactionManager")
    public DataSourceTransactionManager dynamicTransactionManager() throws Exception {
        return new DataSourceTransactionManager(dynamicDataSource());
    }

    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(DataSource dynamicDataSource) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean
                .setMapperLocations(resolver.getResources(
                        "classpath*:/com.mapper/*.xml"));
        sqlSessionFactoryBean.setConfigLocation(resolver.getResource("classpath:/mybatis/mybatis-config.xml"));
        SqlSessionFactory res = sqlSessionFactoryBean.getObject();
        return res;
    }

}

        用起来就是设置一下threadlocal,用完记得清除

DataSourceThreadLocal.setDB(db);
        LOG.info(LOG_TITLE, "change datasource:{}", db);
        // mapper做什么
        LOG.info(LOG_TITLE, "datasource res:{}", JSONUtil.toJsonNoException(res));
        DataSourceThreadLocal.clearDB();
        return res;

四、原理

1、设计        

        在分析mybatis的动态数据源原理之前,先思考一下实现需要怎么设计,设计都是大差不离的,有了设计理念才知道看mybatis哪里的代码。

        如果自己做,就是要考虑数据库的信息放在哪里,真到了使用的时候其实就是找到数据库的链接地址、账号、密码然后建立网络链接,通过链接进行比特流传输。所以可以分为以下几步:

        1、数据库初始化,把数据库的链接地址账号密码存到本地缓存里面去,放在hashmap里面是方便安全的,因为初始化之后没有改动只有取数,而且数据库名称和信息的键值对也方便get。

        2、用的时候要传数据库名称,从缓存里面拿数据库信息

        3、拿着数据库信息,建立链接

         这样看起来主要的点在于找数据库信息和建立连接的时候,那么就可以往这里找mybatis的代码。

2、Mybatis实现

        首先要进入他的查询逻辑

        这里可以看到获取链接了

        在这个本地缓存里面存放了各个数据库的信息以及当前默认的数据库

         没有建立过链接就准备开启链接

        在工具类里面建立连接

        这里已经所有拿到数据库信息了,接下来就是选择哪个进行连接初始化

         就是这里拿lookupKey作为键,从map里面拿数据库信息,如果没有设置的话就会拿默认数据源,由此也可以看出ThreadLocal类里面其实不需要给默认值。作者写了是因为便于理解,后面的同事不一定了解这些原理。

        这里还有个坑在,之所以mybatis会判断当前链接是否为null,是因为在同一个事务当中链接复用,这时候切换数据库就不生效了。

        通常每次查询Mybatis都会执行一次openConnection方法来获取新的数据库连接。这样可以确保每次查询都是在独立的事务中执行,避免数据的混乱和并发问题。

public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}

五、总结

        做法和原理就这些了,还有一些踩的坑忘了,使用有疑问的同学可以评论区交流下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1011930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【案例教学】华为云API图引擎服务 GES的便捷性—AI帮助快速处理图片小助手

云服务、API、SDK&#xff0c;调试&#xff0c;查看&#xff0c;我都行 阅读短文您可以学习到&#xff1a;人工智能AI快速处理图片 1 IntelliJ IDEA 之API插件介绍 API插件支持 VS Code IDE、IntelliJ IDEA等平台、以及华为云自研 CodeArts IDE&#xff0c;基于华为云服务提供…

LeetCode //C - 637. Average of Levels in Binary Tree

637. Average of Levels in Binary Tree Given the root of a binary tree, return the average value of the nodes on each level in the form of an array. Answers within 1 0 − 5 10^{-5} 10−5 of the actual answer will be accepted. Example 1: Input: root [3…

学Python的漫画漫步进阶 -- 第十三步

学Python的漫画漫步进阶 -- 第十三步 十三、图形用户界面13.1 Python中的图形用户界面开发库13.2 安装wxPython13.3 第一个wxPython程序13.4 自定义窗口类13.5 在窗口中添加控件13.6 事件处理13.7 布局管理13.7.1 盒子布局管理器13.7.2 动动手——重构事件处理示例13.7.3 动动手…

赴日开发工程师工作怎么找?

想要做赴日开发工作的途径有一下几种&#xff1a;一个是自己去联系日本的企业&#xff0c;当然这种的前提是你日语完全没有问题&#xff0c;但是现在很少有企业愿意直接与求职人员沟通。第二个就是你有IT技术&#xff0c;但是不会日语&#xff0c;年纪不大的可以来日本读个学校…

单目标追踪——【工具】汉明窗(Hamming window)

目录 汉明窗&#xff08;Hamming window&#xff09;原理作用代码实例可视化总结 汉明窗&#xff08;Hamming window&#xff09; 原理 汉明&#xff08;Hanning&#xff09;窗可以看成是升余弦窗的一个特例&#xff0c;汉宁窗可以看作是3个矩形时间窗的频谱之和&#xff0c;…

webpack实战:最新QQ音乐sign参数加密分析

文章目录 1. 写在前面2. 接口抓包分析3. 扣webpack代码4. 补浏览器环境5. 验证加密结果 1. 写在前面 现在&#xff01;很多的网站使用Webpack加载和处理JS文件。所以对于使用了Webpack加载的JS代码&#xff0c;一旦它们被打包并在浏览器中执行&#xff0c;通常是难以直接阅读和…

选择正确的开发框架:构建高效、可维护的应用程序

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 引言 在现代软件开发中…

python如何打包成应用

前几天有学生问&#xff0c;开发好的python代码如何打包给对方使用&#xff1f;对方没有python的安装执行环境。 python是一个强大的编程开发工具&#xff0c;它不仅仅是做一些命令行或脚本运行工具&#xff0c;还可以开发桌面、web等应用。 本文就介绍使用pyinstall如何把py…

GIS前端编程-地理事件动态模拟

GIS前端编程-地理事件动态模拟 动画特效功能图形闪烁要素轨迹移动 动画特效功能 目前&#xff0c;GIS应用除了涉及地理位置信息&#xff0c;还要结合时间维度&#xff0c;这样才能更加真实地模拟现实世界的事物。因此在实际项目应用中&#xff0c;静态的&#xff08;位置固定不…

Redis混合模式持久化原理

前言 前面文章中我们也介绍过Redis的持久化方式有两种&#xff1a;rdb持久化和aof持久化&#xff0c;具体详情可查看之前文章redis持久化。rdb持久化还是aof持久化它们都有各自的缺点。 rdb和aof缺点 rdb持久化&#xff1a;由于是定期对内存数据快照进行持久化&#xff0c;因此…

【智能家居-大模型】行业专题报告:ChatGPT等大模型催化智能家居行业发展

&#xff08;报告出品方/作者&#xff1a;华安证券&#xff0c;马远方&#xff09; 1 智能家居&#xff1a;ChatGPT 等大模型为行业发展带来新机遇 1.1 现状&#xff1a;智能家居产品的用户体验&#xff08;交互能力、智能化水 平&#xff09;及安全性待提升 智能家居&#…

编程获取图像中的圆半径

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 即将推出EmguCV的教程&#xff0c;请大家还稍作等待。 之前网友咨询如何获得图像中圆形的半径&#xff0c;其中有两个十字作为标定…

如何实现一个简单的深度优先搜索(DFS)算法?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 实现深度优先搜索⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前…

Qt Quick 之 QML 与 C++ 混合编程详解

Qt Quick 之 QML 与 C 混合编程详解 一、Qt Quick 之 QML 与 C 混合编程详解在 QML 中使用 C 类和对象实现可以导出的 C 类Q_INVOKABLE 宏Q_ENUMSQ_PROPERTY注册一个 QML 中可用的类型注册 QML 类型在 QML 中导入 C 注册的类型完整的 colorMaker 实例导出一个 C 对象为 QML 的属…

伴随矩阵与可逆矩阵

目录 伴随矩阵的概念与公式 可逆矩阵的概念与定理 逆矩阵的运算性质 伴随矩阵的概念与公式 伴随矩阵是一个与给定矩阵相关的矩阵&#xff0c;它的元素是给定矩阵的代数余子式。 给定一个nn的方阵A&#xff0c;其元素为aij&#xff0c;则A的伴随矩阵adj(A)是一个nn的方阵&…

EDI 许可证申请对网站有哪些要求?

EDI 许可证申请对网站有哪些要求&#xff1f; 1、电商类平台&#xff1b; 2、二手交易类平台&#xff1b; 3、外卖类平台&#xff1b; 4、票务交易类平台&#xff1b; 5、智能数据处理平台。 ​网站如果是上述类型&#xff0c;那就要办理EDI许可证&#xff0c;但EDI许可证…

北航投资携核心医疗获2023年度十佳投资案例

近日&#xff0c;全球PE论坛联合财新数据发布了2022-2023年度中国PE/VC行业评选结果&#xff0c;北航投资携核心医疗荣获2022-2023年度中国PE/VC十佳投资案例大奖。 经过7年的发展&#xff0c;北航投资的各项业务正密集地进入收获期&#xff0c;业务增长飞轮持续加速&#xff0…

vue拖拽插件 - Sortable

官网地址&#xff1a;Sortable.js中文网 使用方法&#xff1a; 1. npm安装 npm install sortablejs --save 2. 在组件中引入插件 import Sortable from "sortablejs"; 3. 给要拖动的table加上id用来获取dom&#xff0c;记得加row-key&#xff0c;不然会有显示问题 …

苹果“FindMy”APP

“FindMy”是一项 Apple 服务&#xff0c;可以定位设备。在 iOS 13 之前&#xff0c;Apple将该服务拆分为单独的应用程序&#xff1a;“查找我的 iPhone”&#xff08;或 iPad 或 Mac&#xff09;和“查找我的朋友”。该服务适用于iPhone、iPad、Mac、Apple Watch、AirPods、Ai…

麒麟v10安装Redis(ARM架构)

下载Redis安装包 华为开源镜像站_软件开发服务_华为云 上面的选择一个下载 或者用命令下载 wget https://repo.huaweicloud.com/kunpeng/yum/el/7/aarch64/Packages/bigdata/redis-5.0.5-1.el7.aarch64.rpm 检查是否已经安装Redis rpm -qa | grep redis将包卸载掉 rpm -e -…