时间:2021-05-19
Spring多数据源实现的方式大概有2中,一种是新建多个MapperScan扫描不同包,另外一种则是通过继承AbstractRoutingDataSource实现动态路由。今天作者主要基于后者做的实现,且方式1的实现比较简单这里不做过多探讨。
实现方式
方式1的实现(核心代码):
@Configuration@MapperScan(basePackages = "com.goofly.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate")public class DataSource1Config1 { @Bean(name = "dataSource1") @ConfigurationProperties(prefix = "spring.datasource.test1") @Primary public DataSource testDataSource() { return DataSourceBuilder.create().build(); } // .....略}@Configuration@MapperScan(basePackages = "com.goofly.test2", sqlSessionTemplateRef = "test1SqlSessionTemplate")public class DataSourceConfig2 { @Bean(name = "dataSource2") @ConfigurationProperties(prefix = "spring.datasource.test2") @Primary public DataSource testDataSource() { return DataSourceBuilder.create().build(); } // .....略}方式2的实现(核心代码):
public class DynamicRoutingDataSource extends AbstractRoutingDataSource { private static final Logger log = Logger.getLogger(DynamicRoutingDataSource.class); @Override protected Object determineCurrentLookupKey() { //从ThreadLocal中取值 return DynamicDataSourceContextHolder.get(); }}第1种方式虽然实现比较加单,劣势就是不同数据源的mapper文件不能在同一包名,就显得不太灵活了。所以为了更加灵活的作为一个组件的存在,作者采用的第二种方式实现。
设计思路
源码解读
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,如下:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { private Map<Object, Object> targetDataSources; private Object defaultTargetDataSource; private boolean lenientFallback = true; private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); private Map<Object, DataSource> resolvedDataSources; private DataSource resolvedDefaultDataSource; protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; } /** * Determine the current lookup key. This will typically be * implemented to check a thread-bound transaction context. * <p>Allows for arbitrary keys. The returned key needs * to match the stored lookup key type, as resolved by the * {@link #resolveSpecifiedLookupKey} method. */ protected abstract Object determineCurrentLookupKey(); //........略targetDataSources是一个map结构,保存了key与数据源的对应关系;
dataSourceLookup是一个DataSourceLookup类型,默认实现是JndiDataSourceLookup。点开该类源码会发现,它实现了通过key获取DataSource的逻辑。当然,这里可以通过setDataSourceLookup()来改变其属性,因为关于此处有一个坑,后面会讲到。
public class JndiDataSourceLookup extends JndiLocatorSupport implements DataSourceLookup { public JndiDataSourceLookup() { setResourceRef(true); } @Override public DataSource getDataSource(String dataSourceName) throws DataSourceLookupFailureException { try { return lookup(dataSourceName, DataSource.class); } catch (NamingException ex) { throw new DataSourceLookupFailureException( "Failed to look up JNDI DataSource with name '" + dataSourceName + "'", ex); } }}组件使用
多数据源
单数据源
为了让使用者能够用最小的改动实现最好的效果,作者对单数据源的多种配置做了兼容。
示例配置1(配置数据源名称):
spring.datasource.master.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=falsespring.datasource.master.username = rootspring.datasource.master.password = 123456spring.datasource.master.driverClassName = com.mysql.jdbc.Driverspring.datasource.master.validationQuery = truespring.datasource.master.testOnBorrow = true# mapper包路径mapper.basePackages = com.goofly.xli.multidb.demo.mapper# 主数据源名称spring.maindb=master示例配置2(不配置数据源名称):
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=falsespring.datasource.username = rootspring.datasource.password = 123456spring.datasource.driverClassName = com.mysql.jdbc.Driverspring.datasource.validationQuery = truespring.datasource.testOnBorrow = true# mapper包路径mapper.basePackages = com.goofly.xli.multidb.demo.mapper踩坑之路
多数据源的循环依赖
解决方案:
在Spring boot启动的时候排除DataSourceAutoConfiguration即可。如下:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})public class DBMain { public static void main(String[] args) { SpringApplication.run(DBMain.class, args); }}但是作者在创建多数据源的时候由于并未创建多个DataSource的Bean,而是只创建了一个即需要做动态数据源的那个Bean。 其他的DataSource则直接创建实例然后存放在Map里面,然后再设置到DynamicRoutingDataSource#setTargetDataSources即可。
因此这种方式也不会出现循环依赖的问题!
动态刷新数据源
笔者在设计之初是想构建一个动态刷新数据源的方案,所以利用了SpringCloud的@RefreshScope去标注数据源,然后利用RefreshScope#refresh实现刷新。但是在实验的时候发现由Druid创建的数据源会因此而关闭,由Spring的DataSourceBuilder创建的数据源则不会发生任何变化。
最后关于此也没能找到解决方案。同时思考,如果只能的可以实现动态刷新的话,那么数据源的原有连接会因为刷新而中断吗还是会有其他处理?
多数据源事务
有这么一种特殊情况,一个事务中调用了两个不同数据源,这个时候动态切换数据源会因此而失效。
翻阅了很多文章,大概找了2中解决方案,一种是Atomikos进行事务管理,但是貌似性能并不是很理想。
另外一种则是通过优先级控制,切面的的优先级必须要大于数据源的优先级,用注解@Order控制。
此处留一个坑!
git代码地址
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
前言本篇文章主要讲述的是springboot整合mybatis、druid和pagehelper并实现多数据源和分页。其中springboot整合mybatis
本章目标整合Mybatis,并集成Druid数据源可视化监控Druid数据源使用JPA生成数据表利用注解实现数据库的事物利用注解动态配置数据源全局异常捕获校验请
SpringBoot集成MyBatis在集成MyBatis前,我们先配置一个druid数据源。SpringBoot集成druiddruid有很多个配置选项,使用
本文实例讲述了springboot配置DRUID数据源的方法。分享给大家供大家参考,具体如下:druid是阿里开源的数据库连接池。开发时整合druid数据源过程
前言在上一篇学习springboot中,整合了mybatis、druid和pagehelper并实现了多数据源的操作。本篇主要是介绍和使用目前最火的搜索引擎el