本文共 6658 字,大约阅读时间需要 22 分钟。
日常开发中,连接多个数据库是一个很常见的需求,我们的系统是基于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的几个和数据库连接相关的核心类。
类 | 说明 |
---|---|
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);
现在的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方法即可。
转载地址:http://zgrvl.baihongyu.com/