spring mybaits多数据源动态切换

场景说明

日常开发中,连接多个数据库是一个很常见的需求,我们的系统是基于spring boot+mybatis进行数据库的操作,网上常见的思路是基于不同的数据库创建不同的bean,大概的实现方式如下:


package com.joylee.fd.crontab.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.joylee.fd.crontab.mapper.distribution", sqlSessionTemplateRef  = "distributionSqlSessionTemplate")
public class DistributionDBConfiguration {
        @Bean(name = "distributionDataSource")
        @ConfigurationProperties(prefix = "spring.datasource")
        @Primary
        public DataSource distributionDataSource() {
            return DataSourceBuilder.create().build();
        }

        @Bean(name = "distributionSqlSessionFactory")
        @Primary
        public SqlSessionFactory distributionSqlSessionFactory(@Qualifier("distributionDataSource") DataSource dataSource) throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dataSource);
            bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:com/joylee/distribution/mapper/*.xml"));
            return bean.getObject();
        }

        @Bean(name = "distributionDataSourceTransactionManager")
        @Primary
        public DataSourceTransactionManager distributionTransactionManager(@Qualifier("distributionDataSource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }

        @Bean(name = "distributionSqlSessionTemplate")
        @Primary
        public SqlSessionTemplate distributionSqlSessionTemplate(@Qualifier("distributionSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
            return new SqlSessionTemplate(sqlSessionFactory);
        }

}

类似方式,创建多个Configuration以及多个Bean,因为设为@Configuration,所以在启动的时候Bean都会创建,然后使用的时候只需要将不同的数据库mapper放在设置好的classpath(第35行代码),默认走@Primary中的数据库连接,如果需要走其他的,只要在数据库DAO中加上@@qualifier("bean名")即可。

此方案网上很多实现,这里就是细说了。有兴趣大家可以去搜索一下spring boot mybatis多数据库 解决方案。

此方案适用于数据库数量固定这样的需求,如果数据库是动态实时修改的,那么该怎么处理呢,或者数据库数量很多而且一直在扩充(如分库场景),这样处理显然不行,那么我们应该怎么处理呢?这里先说说解决思路。

基于Mybatis实现思路

为了了解mybatis,这里先简单介绍下mybatis的几个和数据库连接相关的核心类。

说明
SqlSessionFactory SqlSession的工厂,负责创建SqlSession
SqlSession mybatis的核心api,负责和数据库交互的回话,该类的方法负责执行数据库的操作
Configuration mybaits的配置类
Environment 数据库环境类,主要是配置事务和数据库连接
MappedStatement 这两个类负责管理具体需要执行的内和方法
*Handler 主要是基于执行的方法输入和输出参数类型转换处理

mybatis的实现代码结构还是比较容易理解的,我们这里重点管理数据库连接的切换,所有我们重点关注的主要是:SqlSessionFactory、SqlSession、Configuration、Environment,当然还有一个datasouce类,这个类是JDBC的类,用来管理数据库连接。

我最开始的思路就是就是绕过SqlSessionFactory Bean创建,因为Bean的生命周期是在程序启动的时候执行的,看起来是无法改变的。所以我考虑了如下的方式:

针对每一个数据库连接,在使用的时候去创建一个新的SqlSessionFactory,通过mybatis的api new SqlSessionFactory().build()来创建多个SqlSessionFactory,然后根据SqlSessionFactory来创建SqlSession,后面思考了一下,这是一个非常严重的错误,SqlSessionFactory本来就是用来进行SqlSession管理的,显然创建多个SqlSessionFactory是非常不合适的,SqlSessionFactory最好是单例,SqlSessionFactory可以根据不同的数据库创建不同的SqlSession。

基于java mybatis实现,大概的步骤就是:

//代码来自mybaits官网
DataSource dataSource = BaseDataTest.createBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();

Environment environment = new Environment("development", transactionFactory, dataSource);

Configuration configuration = new Configuration(environment);
configuration.setLazyLoadingEnabled(true);
configuration.setEnhancementEnabled(true);
configuration.getTypeAliasRegistry().registerAlias(Blog.class);
configuration.getTypeAliasRegistry().registerAlias(Post.class);
configuration.getTypeAliasRegistry().registerAlias(Author.class);
configuration.addMapper(BoundBlogMapper.class);
configuration.addMapper(BoundAuthorMapper.class);

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(configuration);

基于spring mybatis 实现思路

现在的java项目几乎都是基于spring进行开发,对于这样的需求,肯定是可以以spring的方式进行解决的,我陷入了一个误区,一直收到bean在创建后很难修改这个思路的影响,放弃spring bean管理的方式,但是其实这也是一个错误的方向。

spring bean的确是程序启动的时候就完成了bean的创建,但是每个bean本身是有提供很多方法和属性的,其实bean的很多属性是可以修改的,SqlSessionFactorybean肯定会有这样的属性,果然,我们只要获取到程序启动时创建的bean,然后修改属性的值就可以了。

 /**
     * @param corpDatabaseBO 数据库实体
     * @return SqlSessionFactory
     * @throws PropertyVetoException
     */
    public  SqlSessionFactory changeSqlSessionFactory(CorpDatabaseBO corpDatabaseBO) throws Exception {
        //获取当前SqlSessionFactory bean
        
        SqlSessionFactory bean = SpringUtils.getBean(SqlSessionFactory.class);
        
        if(bean==null){
            throw new NullPointerException("default SqlSessionFactory bean is not created");
            
        }
        
        logger.info(String.format("当前的sqlsessionfactory为:%s",bean.getConfiguration().getEnvironment().getDataSource().getConnection().getCatalog()));
        
        //因为业务需要,我用的是sqlserver
        SQLServerDataSource sqlServerDataSource = new SQLServerDataSource();
        sqlServerDataSource.setServerName(corpDatabaseBO.getUrl());
        sqlServerDataSource.setDatabaseName(corpDatabaseBO.getDatabasename());
        sqlServerDataSource.setUser(corpDatabaseBO.getUsername());
        sqlServerDataSource.setPassword(corpDatabaseBO.getPassword());
        sqlServerDataSource.setPortNumber(corpDatabaseBO.getPort());

        //数据库连接池用的是Hikari
        HikariDataSource hikariDataSource = new HikariDataSource();
        hikariDataSource.setDataSource(sqlServerDataSource);

        TransactionFactory transactionFactory =  new JdbcTransactionFactory();
        Environment environment = new Environment(corpDatabaseBO.getDatabasename(), transactionFactory, hikariDataSource);

//        修改environment,这样就可以修改数据库地址了。
        bean.getConfiguration().setEnvironment(environment);
        return bean;
    }

在第30行:

bean.getConfiguration().setEnvironment(environment);

通过bean获取configuration,然后再设置Environment 即可修改数据库连接。每次需要执行数据库切换的时候,只要重新调用changeSqlSessionFactory方法即可。

总结

  • 开发中遇到问题,在自己没有很好的解决思路的情况下,可以去网上查找相关的资料,但是有时候,因为自己业务的特殊性,其实网上是没有很好的解决方案的,甚至网上的一些解决方案并非一个很好的解决方案,我们参考别人方案的时候,一定要弄清楚代码每一层的意思,是否是自己真正需要的,而不是简单的copy。
  • 当网上也没有解决方案的时候,建议多花点时间去阅读源码,因为这个功能,虽然不至于阅读了mybatis所有的源码,但是对mybatis整体的结构有了一个清晰的认识,大部分源码也有阅读,这算是此次最大的收获。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,373评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,732评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,163评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,700评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,036评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,425评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,737评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,421评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,141评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,398评论 2 243
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,908评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,276评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,907评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,018评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,772评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,448评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,325评论 2 261

推荐阅读更多精彩内容