springboot动态数据源的实现⽅法(Mybatis+Druid)
Spring多数据源实现的⽅式⼤概有2中,⼀种是新建多个MapperScan扫描不同包,另外⼀种则是通过继承AbstractRoutingDataSource实现动态路由。今天作者主要基于后者做的实现,且⽅式1的实现⽐较简单这⾥不做过多探讨。实现⽅式
⽅式1的实现(核⼼代码):
@Configuration
@MapperScan(basePackages = "st1", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSource1Config1 {
@Bean(name = "dataSource1")
@ConfigurationProperties(prefix = "st1")
@Primary
public DataSource testDataSource() {
ate().build();
}
// .....略
}
@Configuration
@MapperScan(basePackages = "st2", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSourceConfig2 {
@Bean(name = "dataSource2")
@ConfigurationProperties(prefix = "st2")
@Primary
public DataSource testDataSource() {
ate().build();
}
// .....略
}
⽅式2的实现(核⼼代码):
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private static final Logger log = Logger(DynamicRoutingDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
//从ThreadLocal中取值
();
}
}
第1种⽅式虽然实现⽐较加单,劣势就是不同数据源的mapper⽂件不能在同⼀包名,就显得不太灵活了。所以为了更加灵活的作为⼀个组件的存在,作者采⽤的第⼆种⽅式实现。
设计思路
1. 当请求经过被注解修饰的类后,此时会进⼊到切⾯逻辑中。
2. 切⾯逻辑会获取注解中设置的key值,然后将该值存⼊到ThreadLocal中
3. 执⾏完切⾯逻辑后,会执⾏AbstractRoutingDataSource.determineCurrentLookupKey()⽅法,然后从ThreadLocal中获
取之前设置的key值,然后将该值返回。
4. 由于AbstractRoutingDataSource的targetDataSources是⼀个map,保存了数据源key和数据源的对应
关系,所以能够顺利
的到该对应的数据源。
源码解读
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() {
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = (lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = solvedDefaultDataSource;
}
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);
}
}
}
组件使⽤
多数据源
# db1
spring.datasource.master.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false spring.datasource.master.username = root
spring.datasource.master.password = 123456
spring.datasource.master.driverClassName = sql.jdbc.Driver
spring.datasource.master.validationQuery = true
spring.stOnBorrow = true
## db2
spring.datasource.slave.url = jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=utf8&useSSL=false spring.datasource.slave.username = root
spring.datasource.slave.password = 123456
spring.datasource.slave.driverClassName = sql.jdbc.Driver
spring.datasource.slave.validationQuery = true
spring.stOnBorrow = true
#主数据源名称
spring.maindb=master
#mapperper包路径
mapper.basePackages =com.btps.xli.multidb.demo.mapper
单数据源
为了让使⽤者能够⽤最⼩的改动实现最好的效果,作者对单数据源的多种配置做了兼容。
⽰例配置1(配置数据源名称):
spring.datasource.master.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false spring.datasource.master.username = root
spring.datasource.master.password = 123456
spring.datasource.master.driverClassName = sql.jdbc.Driver
spring.datasource.master.validationQuery = true
spring.stOnBorrow = true
# mapper包路径
mapper.basePackages = fly.xli.multidb.demo.mapper
# 主数据源名称
spring.maindb=master
⽰例配置2(不配置数据源名称):
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false spring.datasource.username = root
spring.datasource.password = 123456
spring.datasource.driverClassName = sql.jdbc.Driver
spring.datasource.validationQuery = true
stOnBorrow = true
# mapper包路径
mapper.basePackages = fly.xli.multidb.demo.mapper
多数据源的循环依赖
Description:
The dependencies of some of the beans in the application context form a cycle:
happinessController (field private com.db.service.HappinessService ller.HappinessController.happinessService)
happinessServiceImpl (field private com.db.mapper.MasterDao com.db.service.HappinessServiceImpl.masterDao)
masterDao defined in file [E:\GitRepository\framework-gray\test-db\target\classes\com\db\mapper\MasterDao.class]
sqlSessionFactory defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class]
┌─────┐
| dynamicDataSource defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class]
↑↓
| firstDataSource defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class]
↑↓
| dataSourceInitializer
解决⽅案:
在Spring boot启动的时候排除DataSourceAutoConfiguration即可。如下:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class DBMain {
public static void main(String[] args) {
SpringApplication.run(DBMain.class, args);springcloud和springboot
}
}
但是作者在创建多数据源的时候由于并未创建多个DataSource的Bean,⽽是只创建了⼀个即需要做动态数据源的那个Bean。其他的DataSource则直接创建实例然后存放在Map⾥⾯,然后再设置到
DynamicRoutingDataSource#setTargetDataSources即可。
因此这种⽅式也不会出现循环依赖的问题!
动态刷新数据源
笔者在设计之初是想构建⼀个动态刷新数据源的⽅案,所以利⽤了SpringCloud的@RefreshScope去标注数据源,然后利⽤RefreshScope#refresh实现刷新。但是在实验的时候发现由Druid创建的数据源会因此⽽关闭,由Spring的DataSourceBuilder 创建的数据源则不会发⽣任何变化。
最后关于此也没能到解决⽅案。同时思考,如果只能的可以实现动态刷新的话,那么数据源的原有连接会因为刷新⽽中断吗还是会有其他处理?
多数据源事务
有这么⼀种特殊情况,⼀个事务中调⽤了两个不同数据源,这个时候动态切换数据源会因此⽽失效。
翻阅了很多⽂章,⼤概了2中解决⽅案,⼀种是Atomikos进⾏事务管理,但是貌似性能并不是很理想。
另外⼀种则是通过优先级控制,切⾯的的优先级必须要⼤于数据源的优先级,⽤注解@Order控制。
此处留⼀个坑!
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。