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

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

代码展示

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

  1. 数据配置
  1. @Configuration
  2. @PropertySource({ "classpath:jdbc.yml" })
  3. @EnableTransactionManagement(proxyTargetClass = true)
  4. public class DataConfig {
  5. @Autowired
  6. private Environment env ;
  7. /**
  8. * 将jdbc相关的异常转换为spring的异常类型
  9. */
  10. @Bean
  11. public BeanPostProcessor persistenceTransLation(){
  12. return new PersistenceExceptionTranslationPostProcessor() ;
  13. }
  14. /**
  15. * 多数据源
  16. * @return
  17. */
  18. @Bean
  19. public DynamicDataSource dynamicDataSource(){
  20. DynamicDataSource dynamicDataSource = new DynamicDataSource();
  21. Map<Object,Object> sourceMap = new HashMap<>();
  22. //取得所有的datasource,DataSourceEnum里存放数据源的唯一标识
  23. EnumSet<DataSourceEnum> enums = EnumSet.allOf(DataSourceEnum.class);
  24. for(DataSourceEnum dataSource:enums){
  25. // map存放数据源的key和数据源
  26. sourceMap.put(dataSource.getKey(),generateDataSource(dataSource.getKey()));
  27. }
  28. // 重点
  29. dynamicDataSource.setTargetDataSources(sourceMap);
  30. dynamicDataSource.setDefaultTargetDataSource(sourceMap.get(DataSourceEnum.TEST.getKey()));
  31. return dynamicDataSource;
  32. }
  33. // 读取配置文件,创建数据源对象
  34. private EncryptDataSource generateDataSource(String key){
  35. EncryptDataSource dataSource
  36. = new EncryptDataSource();
  37. key = key.toLowerCase() ;
  38. String url = "jdbc.url."+key;
  39. String username = "jdbc.username."+key;
  40. String password = "jdbc.password."+key;
  41. dataSource.setDriverClassName("com.sybase.jdbc4.jdbc.SybDataSource");//SybDriver
  42. dataSource.setUrl(env.getProperty(url));
  43. dataSource.setUsername(env.getProperty(username));
  44. dataSource.setPassword(env.getProperty(password));
  45. //配置连接池
  46. dataSource.setInitialSize(Integer.parseInt(env.getProperty("jdbc.initialSize")));
  47. dataSource.setMaxIdle(Integer.parseInt(env.getProperty("jdbc.maxIdle")));
  48. dataSource.setMinIdle(Integer.parseInt(env.getProperty("jdbc.minIdle")));
  49. return dataSource;
  50. }
  51. }
  1. 自定义数据源类
  1. public class DynamicDataSource extends AbstractRoutingDataSource {
  2. // 存放数据源的id(唯一标识)
  3. private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<>() ;
  4. // 重点
  5. @Override
  6. protected Object determineCurrentLookupKey() {
  7. return dataSourceHolder.get();
  8. }
  9. // 切换数据源
  10. public static void router(String sourceKey){
  11. if(StrUtil.isEmpty(sourceKey)){
  12. return;
  13. }
  14. if(DataSourceEnum.getSourceByKey(sourceKey)!=null){
  15. //根据法院代码切换
  16. dataSourceHolder.set(DataSourceEnum.getSourceByKey(sourceKey));
  17. }
  18. }
  19. ……
  20. }
  1. 数据源配置(jdbc.yml)
  1. #测试库
  2. jdbc.url.test: jdbc:sybase:Tds:xxx.xxx.xxx.xxx:xx/JUDGE?charset=cp936
  3. jdbc.username.test: fymis
  4. jdbc.password.test: xx

原理分析

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

AbstractRoutingDataSource 类解析

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

  1. public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
  2. @Nullable
  3. private Map<Object, Object> targetDataSources;// 目标数据源map
  4. @Nullable
  5. private Object defaultTargetDataSource;// 默认数据源
  6. private boolean lenientFallback = true;
  7. private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
  8. @Nullable
  9. private Map<Object, DataSource> resolvedDataSources;
  10. @Nullable
  11. private DataSource resolvedDefaultDataSource;
  12. public AbstractRoutingDataSource() {
  13. }
  14. public void setTargetDataSources(Map<Object, Object> targetDataSources) {
  15. this.targetDataSources = targetDataSources;
  16. }
  17. public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
  18. this.defaultTargetDataSource = defaultTargetDataSource;
  19. }
  20. // 初始化 Bean 时执行
  21. public void afterPropertiesSet() {
  22. if (this.targetDataSources == null) {
  23. throw new IllegalArgumentException("Property 'targetDataSources' is required");
  24. } else {
  25. // 将targetDataSources属性的值赋值给resolvedDataSources,后续需要用到resolvedDataSources
  26. this.resolvedDataSources = new HashMap(this.targetDataSources.size());
  27. this.targetDataSources.forEach((key, value) -> {
  28. Object lookupKey = this.resolveSpecifiedLookupKey(key);
  29. DataSource dataSource = this.resolveSpecifiedDataSource(value);
  30. this.resolvedDataSources.put(lookupKey, dataSource);
  31. });
  32. if (this.defaultTargetDataSource != null) {
  33. this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
  34. }
  35. }
  36. }
  37. // 重写了 getConnection 方法,ORM 框架执行语句前会调用该处
  38. @Override
  39. public Connection getConnection() throws SQLException {
  40. return this.determineTargetDataSource().getConnection();
  41. }
  42. // 同上
  43. @Override
  44. public Connection getConnection(String username, String password) throws SQLException {
  45. return this.determineTargetDataSource().getConnection(username, password);
  46. }
  47. // 重点
  48. protected DataSource determineTargetDataSource() {
  49. Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
  50. // 调用我们重写的determineCurrentLookupKey方法,返回的是数据源的唯一标识
  51. Object lookupKey = this.determineCurrentLookupKey();
  52. // 从map中查询改标识对应的数据源,然后返回该数据源,打开对应连接
  53. DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
  54. if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
  55. dataSource = this.resolvedDefaultDataSource;
  56. }
  57. if (dataSource == null) {
  58. throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
  59. } else {
  60. return dataSource;
  61. }
  62. }
  63. // 钩子方法,供我们重写
  64. @Nullable
  65. protected abstract Object determineCurrentLookupKey();
  66. }

总结与闲谈

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

  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. uni-app中textarea组件

    textarea组件,官方给出的监听事件有以下事件: 其中一定要注意,当使用 v-model 对表单内容进行双向绑定的时候,@input 事件是在绑定变量变化前触发的,所以如果在input事件内打印绑 ...

  2. day29 继承

    目录 一.property装饰器 应用场景1 应用场景2 应用场景3(场景2优化) 二.继承介绍 1 语法 2 属性查找 3 继承的实现原理 3.1 菱形问题 3.2 继承原理 3.3 深度优先和广度 ...

  3. 利用EasyExcel进行对表格数据的写入

    一导入依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</ ...

  4. SpringBoot学习笔记(十七:异步调用)

    @ 目录 1.@EnableAsync 2.@Async 2.1.无返回值的异步方法 2.1.有返回值的异步方法 3. Executor 3.1.方法级别重写Executor 3.2.应用级别重写Ex ...

  5. 一位Google高管审查了20,000+简历,他发现了这5个致命的错误

    工作与生活的平衡 下班划水摸鱼时间,我比较喜欢浏览一下各类新闻网页,比如说ins,这不,我就在ins上看到了这样的一篇文章,内容很简单,就是简历,但是就是这样一份简历,却让这位Google高管震惊不已 ...

  6. .Net Core+Nginx实现项目负载均衡

    nginx大家如果没用过那或多或少都应该听过,vue的部署.反向代理.负载均衡nginx都能帮你做到. 今天主要说一下nginx负载均衡我们的项目,如下图所示,请求到达nginx,nginx再帮我们转 ...

  7. 解决Kubernetes Pod故障的5个简单技巧

    在很多情况下,你可能会发现Kubernetes中的应用程序没有正确地部署,或者没有正常地工作.今天这篇文章就提供了如何去快速解决这类故障以及一些技巧. 在阅读了这篇文章之后,你还将深入了解Kubern ...

  8. pandas | 如何在DataFrame中通过索引高效获取数据?

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是pandas数据处理专题的第四篇文章,我们一起来聊聊DataFrame中的索引. 上一篇文章当中我们介绍了DataFrame数据结构当 ...

  9. ~~并发编程(十三):信号量,Event,定时器~~

    进击のpython ***** 并发编程--信号量,Event,定时器 本节需要了解的就是: 信号量,以及信号量和互斥锁的区别 了解时间和定时器,以及使用 信号量 信号量也是锁,本质没有变!但是他跟互 ...

  10. 我自己总结的sqlite的命令行命令集

    我自己总结的sqlite 的命令行命令 导入文本数据文件时,设置分隔符为","sql>.separator "," sql>import devic ...