AbstractRoutingDataSource 实现动态数据源切换原理简单分析

写在前面,项目中用到了动态数据源切换,记录一下其运行机制。

代码展示

下面列出一些关键代码,后续分析会用到

  1. 数据配置
@Configuration
@PropertySource({ "classpath:jdbc.yml" })
@EnableTransactionManagement(proxyTargetClass = true)
public class DataConfig { @Autowired
private Environment env ; /**
* 将jdbc相关的异常转换为spring的异常类型
*/
@Bean
public BeanPostProcessor persistenceTransLation(){
return new PersistenceExceptionTranslationPostProcessor() ;
} /**
* 多数据源
* @return
*/
@Bean
public DynamicDataSource dynamicDataSource(){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object,Object> sourceMap = new HashMap<>();
//取得所有的datasource,DataSourceEnum里存放数据源的唯一标识
EnumSet<DataSourceEnum> enums = EnumSet.allOf(DataSourceEnum.class);
for(DataSourceEnum dataSource:enums){
// map存放数据源的key和数据源
sourceMap.put(dataSource.getKey(),generateDataSource(dataSource.getKey()));
}
// 重点
dynamicDataSource.setTargetDataSources(sourceMap);
dynamicDataSource.setDefaultTargetDataSource(sourceMap.get(DataSourceEnum.TEST.getKey()));
return dynamicDataSource;
} // 读取配置文件,创建数据源对象
private EncryptDataSource generateDataSource(String key){
EncryptDataSource dataSource
= new EncryptDataSource();
key = key.toLowerCase() ;
String url = "jdbc.url."+key;
String username = "jdbc.username."+key;
String password = "jdbc.password."+key;
dataSource.setDriverClassName("com.sybase.jdbc4.jdbc.SybDataSource");//SybDriver
dataSource.setUrl(env.getProperty(url));
dataSource.setUsername(env.getProperty(username));
dataSource.setPassword(env.getProperty(password)); //配置连接池
dataSource.setInitialSize(Integer.parseInt(env.getProperty("jdbc.initialSize")));
dataSource.setMaxIdle(Integer.parseInt(env.getProperty("jdbc.maxIdle")));
dataSource.setMinIdle(Integer.parseInt(env.getProperty("jdbc.minIdle")));
return dataSource;
}
}
  1. 自定义数据源类
public class DynamicDataSource extends AbstractRoutingDataSource {
// 存放数据源的id(唯一标识)
private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<>() ; // 重点
@Override
protected Object determineCurrentLookupKey() {
return dataSourceHolder.get();
} // 切换数据源
public static void router(String sourceKey){
if(StrUtil.isEmpty(sourceKey)){
return;
}
if(DataSourceEnum.getSourceByKey(sourceKey)!=null){
//根据法院代码切换
dataSourceHolder.set(DataSourceEnum.getSourceByKey(sourceKey));
}
} …… }
  1. 数据源配置(jdbc.yml)
#测试库
jdbc.url.test: jdbc:sybase:Tds:xxx.xxx.xxx.xxx:xx/JUDGE?charset=cp936
jdbc.username.test: fymis
jdbc.password.test: xx

原理分析

第一部分已将关键代码列出,该部分通过修改后即可实现数据源的切换功能。下面来分析一下流程。

AbstractRoutingDataSource 类解析

只列出了部分方法,需要详细代码请自行移步源码

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
@Nullable
private Map<Object, Object> targetDataSources;// 目标数据源map
@Nullable
private Object defaultTargetDataSource;// 默认数据源
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource; public AbstractRoutingDataSource() {
} public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
} public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
} // 初始化 Bean 时执行
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
} else {
// 将targetDataSources属性的值赋值给resolvedDataSources,后续需要用到resolvedDataSources
this.resolvedDataSources = new HashMap(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
Object lookupKey = this.resolveSpecifiedLookupKey(key);
DataSource dataSource = this.resolveSpecifiedDataSource(value);
this.resolvedDataSources.put(lookupKey, dataSource);
});
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
} }
} // 重写了 getConnection 方法,ORM 框架执行语句前会调用该处
@Override
public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
} // 同上
@Override
public Connection getConnection(String username, String password) throws SQLException {
return this.determineTargetDataSource().getConnection(username, password);
} // 重点
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
// 调用我们重写的determineCurrentLookupKey方法,返回的是数据源的唯一标识
Object lookupKey = this.determineCurrentLookupKey();
// 从map中查询改标识对应的数据源,然后返回该数据源,打开对应连接
DataSource 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 + "]");
} else {
return dataSource;
}
} // 钩子方法,供我们重写
@Nullable
protected abstract Object determineCurrentLookupKey();
}

总结与闲谈

综上,可以列出以下几点描述整个流程:

  1. 自定义类继承 AbstractRoutingDataSource(后文称 ARDS),重写 determineCurrentLookupKey(),返回数据源的唯一标识;
  2. 将数据源名称和数据源封装为 map,调用 ARDS 类的 setTargetDataSources() 设置目标数据源。ARDS 类实现了 InitializingBean 接口,重写了 afterPropertySet()(对该方法不熟悉的话请回顾一下 Bean 的生命周期,该方法在 Bean 的属性注入后执行),该方法内部对 resolvedDataSources 属性赋值(将 targetDataSources 的值放进去),后续会用到 resolvedDataSources ;
  3. ARDS 实现了 DataSource 接口,重写了 getConnection(),当 ORM 框架执行 sql 语句前总是执行 getConnection(),然后就调用到了重写后的 getConnection(),该方法内部调用了 ARDS 类的 determineTargetDataSource()
  4. determineTargetDataSource() 内部调用了自定义类重写的 determineCurrentLookupKey(),返回数据源的映射,然后从 resolvedDataSources(map) 属性获取到数据源,进行后续的操作。

(题外话)想要实现数据源切换可以有两种实现:

  1. 手动切换数据源,每次执行相应操作前调用 router 方法切换;
  2. 还有一种思路就是利用 AOP,设计一个注解,注解内添加数据源唯一标识的属性,然后对方法添加注解,AOP 代码进行拦截,然后将唯一标识赋值给 ThreadLocal 变量即可。

AbstractRoutingDataSource 实现动态数据源切换原理简单分析的更多相关文章

  1. SpringMVC 利用AbstractRoutingDataSource实现动态数据源切换

    SpringMVC 利用AbstractRoutingDataSource实现动态数据源切换 本文转载至:http://exceptioneye.iteye.com/blog/1698064 Spri ...

  2. Spring主从数据库的配置和动态数据源切换原理

    原文:https://www.liaoxuefeng.com/article/00151054582348974482c20f7d8431ead5bc32b30354705000 在大型应用程序中,配 ...

  3. Spring(AbstractRoutingDataSource)实现动态数据源切换--转载

    原始出处:http://linhongyu.blog.51cto.com/6373370/1615895 一.前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目 ...

  4. AbstractRoutingDataSource实现动态数据源切换 专题

    需求:系统中要实现切换数据库(业务数据库和his数据库) 网上很多资料上有提到AbstractRoutingDataSource,大致是这么说的 在Spring 2.0.1中引入了AbstractRo ...

  5. Spring(AbstractRoutingDataSource)实现动态数据源切换

    转自: http://blog.51cto.com/linhongyu/1615895 一.前言 近期一项目A需实现数据同步到另一项目B数据库中,在不改变B项目的情况下,只好选择项目A中切换数据源,直 ...

  6. 利用AbstractRoutingDataSource实现动态数据源切换

    需求:系统中要实现切换数据库(业务数据库和his数据库) 网上很多资料上有提到AbstractRoutingDataSource,大致是这么说的 在Spring 2.0.1中引入了AbstractRo ...

  7. spring AbstractRoutingDataSource实现动态数据源切换

    使用Spring 提供的 AbstractRoutingDataSource 实现 创建 AbstractRoutingDataSource 实现类,负责保存所有数据源与切换数据源策略:public ...

  8. Spring 实现动态数据源切换--转载 (AbstractRoutingDataSource)的使用

    [参考]Spring(AbstractRoutingDataSource)实现动态数据源切换--转载 [参考] 利用Spring的AbstractRoutingDataSource解决多数据源的问题 ...

  9. 【开发笔记】- AbstractRoutingDataSource动态数据源切换,AOP实现动态数据源切换

    AbstractRoutingDataSource动态数据源切换 上周末,室友通宵达旦的敲代码处理他的多数据源的问题,搞的非常的紧张,也和我聊了聊天,大概的了解了他的业务的需求.一般的情况下我们都是使 ...

随机推荐

  1. KMP入门

    First.先上一份最原始的无任何优化的代码(暴力): #include <iostream> #include <cstring> using namespace std; ...

  2. flex-direction和flex-wrap

    当外层容器使用flex布局,并且把flex-direction设置成colum的时候,内层容器的宽度会跟外层容器的宽度保持一致. 在浏览器上的效果如下: 当把外层容器的纵向布局不适用flex-dire ...

  3. Django---进阶12

    目录 Auth模块 方法总结 如何扩展auth_user表 项目开发流程 表设计 作业 Auth模块 """ 其实我们在创建好一个django项目之后直接执行数据库迁移命 ...

  4. jvm之栈、堆

    1. Java Virtual Machine ​ 人群当中,一位叫java的小伙子正向周围一众人群细数着自己取得的荣耀与辉煌.就在此时,c老头和c++老头缓步走来,看着被众人围住的java,c老头感 ...

  5. 数据聚合与分组操作知识图谱-《利用Python进行数据分析》

    所有内容整理自<利用Python进行数据分析>,使用MindMaster Pro 7.3制作,emmx格式,源文件已经上传Github,需要的同学转左上角自行下载或者右击保存图片. 其他章 ...

  6. CppUnit使用和源码解析

    前言 CppUnit是一个开源的单元测试框架,支持Linux和Windows操作系统,在linux上可以直接进行源码编译,得到动态库和静态库,直接链接就可以正常使用,在Windows上可以使用VC直接 ...

  7. 浅谈Python中的深浅拷贝的区别

    深.浅拷贝总结 深拷贝 拷贝可变数据类型,如列表容器: a = [1, 2, [3, 4]] b = copy.deepcopy(a) a 与 b 所指的列表容器的空间地址不一致,即 id(a) != ...

  8. Netty 学习笔记(4) ------ EventLoopGroup

    EventLoopGroup负责管理Channel的事件处理任务,继承自java.util.concurrent包下的Executor,所以其结构类似与线程池,管理多个EventLoop. 而一个Ev ...

  9. Mybatis(六)逆向工程generator

    逆向工程概述: MyBatis的一个主要的特点就是需要程序员自己编写sql,那么如果表太多的话,难免会很麻烦,所以mybatis官方提供了一个逆向工程,可以针对单表自动生成mybatis执行所需要的代 ...

  10. ELK5.6.4+Redis+Filebeat+Nginx(CentOS7.4)

    下载地址: Elasticsearhc: https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.4.tar.gz ...