1、动态数据源:

   在一个项目中,有时候需要用到多个数据库,比如读写分离,数据库的分布式存储等等,这时我们要在项目中配置多个数据库。

2、原理:

    (1)、spring 单数据源获取数据连接过程:

    DataSource --> SessionFactory --> Session
    DataSouce   实现javax.sql.DateSource接口的数据源,
    DataSource  注入SessionFactory,
    从sessionFactory 获取 Session,实现数据库的 CRUD。

  (2)、动态数据源切换:  

    动态数据源原理之一:实现 javax.sql.DataSource接口, 封装DataSource, 在 DataSource 配置多个数据库连接,这种方式只需要一个dataSouce,就能实现多个数据源,最理想的实现,但是需要自己实现DataSource,自己实现连接池,对技术的要求较高,而且自己实现的连接池在性能和稳定性上都有待考验。

    动态数据源原理之二:配置多个DataSource, SessionFactory注入多个DataSource,实现SessionFactory动态调用DataSource,这种方式需要自己实现SessesionFactory,第三方实现一般不支持注入多个DataSource。

    动态数据源原理之三:配置多个DataSource, 在DataSource和SessionFactory之间插入 RoutingDataSource路由,即 DataSource --> RoutingDataSource --> SessionFactory --> Session, 在SessionFactory调用时在 RoutingDataSource 层实现DataSource的动态切换, spring提供了 AbstratRoutingDataSource抽象类, 对动态数据源切换提供了很好的支持, 不需要开发者实现复杂的底层逻辑, 推荐实现方式。

    动态数据源原理之四:配置多个SessionFactory,这种实现对技术要求最低,但是相对切换数据源最不灵活。  

3、实现:

  这里我们使用原理三以读写分离为例,具体实现如下:

  步骤一:配置多个DateSource,使用的基于阿里的 DruidDataSource

 1 <!-- 引入属性文件,方便配置内容修改 -->
 2     <context:property-placeholder location="classpath:jdbc.properties" />
 3
 4
 5     <!-- 数据库链接(主库) -->
 6     <bean id="dataSourceRW" class="com.alibaba.druid.pool.DruidDataSource"
 7         destroy-method="close">
 8         <!-- 基本属性 url、user、password -->
 9         <property name="url" value="${jdbc_url}" />
10         <property name="username" value="${jdbc_username}" />
11         <property name="password" value="${jdbc_password}" />
12
13         <!-- 配置初始化大小、最小、最大 -->
14         <property name="initialSize" value="${druid_initialSize}" />
15         <property name="minIdle" value="${druid_minIdle}" />
16         <property name="maxActive" value="${druid_maxActive}" />
17
18         <!-- 配置获取连接等待超时的时间 -->
19         <property name="maxWait" value="${druid_maxWait}" />
20
21         <property name="validationQuery" value="SELECT 'x'" />
22         <property name="testWhileIdle" value="true" />
23
24         <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
25         <property name="poolPreparedStatements" value="true" />
26         <property name="maxPoolPreparedStatementPerConnectionSize"
27             value="100" />
28
29         <!-- 密码加密 -->
30         <property name="filters" value="config" />
31         <property name="connectionProperties" value="config.decrypt=true" />
32     </bean>
33
34
35         <!-- 数据库链接(只读库) -->
36     <bean id="dataSourceR" class="com.alibaba.druid.pool.DruidDataSource"
37         destroy-method="close">
38         <!-- 基本属性 url、user、password -->
39         <property name="url" value="${jdbc_url_read}" />
40         <property name="username" value="${jdbc_username_read}" />
41         <property name="password" value="${jdbc_password_read}" />
42
43         <!-- 配置初始化大小、最小、最大 -->
44         <property name="initialSize" value="${druid_initialSize}" />
45         <property name="minIdle" value="${druid_minIdle}" />
46         <property name="maxActive" value="${druid_maxActive}" />
47
48         <!-- 配置获取连接等待超时的时间 -->
49         <property name="maxWait" value="${druid_maxWait}" />
50
51         <property name="validationQuery" value="SELECT 'x'" />
52         <property name="testWhileIdle" value="true" />
53
54         <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
55         <property name="poolPreparedStatements" value="true" />
56         <property name="maxPoolPreparedStatementPerConnectionSize"
57             value="100" />
58
59         <!-- 密码加密 -->
60         <property name="filters" value="config" />
61         <property name="connectionProperties" value="config.decrypt=true" />
62     </bean>

View Code

  步骤二:配置 DynamicDataSource

 1 <!-- 动态数据源 -->
 2    <bean id="dynamicDataSource" class="base.dataSource.DynamicDataSource">
 3        <!-- 通过key-value关联数据源 -->
 4        <property name="targetDataSources">
 5            <map>
 6                <entry value-ref="dataSourceRW" key="dataSourceRW"></entry>
 7                <entry value-ref="dataSourceR" key="dataSourceR"></entry>
 8            </map>
 9        </property>
10        <!-- 默认的DataSource配置-->
11        <property name="defaultTargetDataSource" ref="dataSourceR" />
12    </bean>  

View Code

 1 package base.dataSource;
 2
 3 import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 4
 5 public class DynamicDataSource extends AbstractRoutingDataSource{
 6
 7     @Override
 8     protected Object determineCurrentLookupKey() {
 9         return DBContextHolder.getDbType();
10     }
11 }

View Code

  DynamicDataSource 继承了spring 的 AbstractRoutingDataSource 抽象类 实现determineCurrentLookupKey()方法

  determineCurrentLookupKey()方法在 SessionFactory 获取 DataSoure时被调用,AbstractRoutingDataSource 代码:

  1 //
  2 // Source code recreated from a .class file by IntelliJ IDEA
  3 // (powered by Fernflower decompiler)
  4 //
  5
  6 package org.springframework.jdbc.datasource.lookup;
  7
  8 import java.sql.Connection;
  9 import java.sql.SQLException;
 10 import java.util.HashMap;
 11 import java.util.Iterator;
 12 import java.util.Map;
 13 import java.util.Map.Entry;
 14 import javax.sql.DataSource;
 15 import org.springframework.beans.factory.InitializingBean;
 16 import org.springframework.jdbc.datasource.AbstractDataSource;
 17 import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
 18 import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
 19 import org.springframework.util.Assert;
 20
 21 public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
 22     private Map<Object, Object> targetDataSources;
 23     private Object defaultTargetDataSource;
 24     private boolean lenientFallback = true;
 25     private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
 26     private Map<Object, DataSource> resolvedDataSources;
 27     private DataSource resolvedDefaultDataSource;
 28
 29     public AbstractRoutingDataSource() {
 30     }
 31
 32     public void setTargetDataSources(Map<Object, Object> targetDataSources) {
 33         this.targetDataSources = targetDataSources;
 34     }
 35
 36     public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
 37         this.defaultTargetDataSource = defaultTargetDataSource;
 38     }
 39
 40     public void setLenientFallback(boolean lenientFallback) {
 41         this.lenientFallback = lenientFallback;
 42     }
 43
 44     public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
 45         this.dataSourceLookup = (DataSourceLookup)(dataSourceLookup != null?dataSourceLookup:new JndiDataSourceLookup());
 46     }
 47
 48     public void afterPropertiesSet() {
 49         if(this.targetDataSources == null) {
 50             throw new IllegalArgumentException("Property \'targetDataSources\' is required");
 51         } else {
 52             this.resolvedDataSources = new HashMap(this.targetDataSources.size());
 53             Iterator var1 = this.targetDataSources.entrySet().iterator();
 54
 55             while(var1.hasNext()) {
 56                 Entry entry = (Entry)var1.next();
 57                 Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());
 58                 DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());
 59                 this.resolvedDataSources.put(lookupKey, dataSource);
 60             }
 61
 62             if(this.defaultTargetDataSource != null) {
 63                 this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
 64             }
 65
 66         }
 67     }
 68
 69     protected Object resolveSpecifiedLookupKey(Object lookupKey) {
 70         return lookupKey;
 71     }
 72
 73     protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
 74         if(dataSource instanceof DataSource) {
 75             return (DataSource)dataSource;
 76         } else if(dataSource instanceof String) {
 77             return this.dataSourceLookup.getDataSource((String)dataSource);
 78         } else {
 79             throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
 80         }
 81     }
 82
 83     public Connection getConnection() throws SQLException {
 84         return this.determineTargetDataSource().getConnection();
 85     }
 86
 87     public Connection getConnection(String username, String password) throws SQLException {
 88         return this.determineTargetDataSource().getConnection(username, password);
 89     }
 90
 91     public <T> T unwrap(Class<T> iface) throws SQLException {
 92         return iface.isInstance(this)?this:this.determineTargetDataSource().unwrap(iface);
 93     }
 94
 95     public boolean isWrapperFor(Class<?> iface) throws SQLException {
 96         return iface.isInstance(this) || this.determineTargetDataSource().isWrapperFor(iface);
 97     }
 98
 99     protected DataSource determineTargetDataSource() {
100         Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
101         Object lookupKey = this.determineCurrentLookupKey();
102         DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
103         if(dataSource == null && (this.lenientFallback || lookupKey == null)) {
104             dataSource = this.resolvedDefaultDataSource;
105         }
106
107         if(dataSource == null) {
108             throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
109         } else {
110             return dataSource;
111         }
112     }
113
114     protected abstract Object determineCurrentLookupKey();
115 }

View Code

AbstractRoutingDataSource 两个主要变量:  targetDataSources 初始化了 DataSource 的map集合, defaultTargetDataSource 初始化默认的DataSource 并实现了 DataSource的 getConnection() 获取数据库连接的方法,该方法从determineTargetDataSource()获取  DataSource, determineTargetDataSource() 调用了我们 DynamicDataSource 中实现的 determineCurrentLookupKey() 方法获取DataSource(determineCurrentLookupKey()方法返回的只是我们初始化的DataSource Ma  p集合key值, 通过key获取DataSource的方法这里不做赘述,感兴趣自己研究下),determineTargetDataSource()的主要逻辑是获取我们切换的DataSource, 如果没有的话读取默认的DataSource。

在DynamicDataSource中我们定义了一个线程变量DBContextHolder来存放我们切换的DataSource, 防止其它线程覆盖我们的DataSource。

 1 package base.dataSource;
 2
 3 /**
 4  *
 5  * @author xiao
 6  * @date 下午3:27:52
 7  */
 8 public final class DBContextHolder {
 9
10     /**
11      * 线程threadlocal
12      */
13     private static ThreadLocal<String> contextHolder = new ThreadLocal<>();
14
15     private static String DEFAUL_DB_TYPE_RW = "dataSourceKeyRW";
16
17     /**
18      * 获取本线程的dbtype
19      * @return
20      */
21     public static String getDbType() {
22         String db = contextHolder.get();
23         if (db == null) {
24             db = DEFAUL_DB_TYPE_RW;// 默认是读写库
25         }
26         return db;
27     }
28
29     /**
30      *
31      * 设置本线程的dbtype
32      *
33      * @param str
34      */
35     public static void setDbType(String str) {
36         contextHolder.set(str);
37     }
38
39     /**
40      * clearDBType
41      *
42      * @Title: clearDBType
43      * @Description: 清理连接类型
44      */
45     public static void clearDBType() {
46         contextHolder.remove();
47     }
48 }

View Code

  至此我们获取DataSource的逻辑已完成, 接下来我们要考虑 设置DataSource, 即为DBContextHolder, set值。我们在代码中调用DBContextHolder.set()来设置DataSource,理论上可以在代码的任何位置设置, 不过为了统一规范,我们通过aop来实现,此时我们面临的问题,在哪一层切入, 方案一: 在dao层切入,dao封装了数据库的CRUD,在这一层切入控制最灵活,但是我们一般在service业务层切入事务,如果在dao层切换数据源,会遇到事务无法同步的问题,虽然有分布式事务机制,但是目前成熟的框架很难用,如果使用过 就会知道分布式事务是一件非常恶心的事情,而且分布式事务本就不是一个好的选择。方案二: 在service业务层切入,可以避免事务问题,但也相对影响了数据源切换的灵活性,这里要根据实际情况灵活选择,我们采用的在service业务层切入,具体实现如下:

步骤三:实现aop

 1 package base.dataSource.aop;
 2
 3 import java.util.Map;
 4
 5 import org.aspectj.lang.JoinPoint;
 6 import org.springframework.core.Ordered;
 7
 8 import base.dataSource.DBContextHolder;
 9
10 /**
11  * 动态数据源切换aop
12  * @author xiao
13  * @date 2015年7月23日下午4:17:13
14  */
15 public final class DynamicDataSourceAOP implements Ordered{
16
17
18     /**
19      * 方法, 数据源应映射规则map
20      */
21     Map<String, String> methods;
22
23     /**
24      * 默认数据源
25      */
26     String defaultDataSource;
27
28
29     public String getDefaultDataSource() {
30         return defaultDataSource;
31     }
32
33     public void setDefaultDataSource(String defaultDataSource) {
34         if(null == defaultDataSource || "".equals(defaultDataSource)){
35             throw new NullPointerException("defaultDataSource Must have a default value");
36         }
37         this.defaultDataSource = defaultDataSource;
38     }
39
40     public Map<String, String> getMethods() {
41         return methods;
42     }
43
44     public void setMethods(Map<String, String> methods) {
45         this.methods = methods;
46     }
47
48     /**
49      * before 数据源切换
50      *
51      * @param pjp
52      * @throws Throwable
53      */
54     public void dynamicDataSource(JoinPoint pjp) throws Throwable {
55         DBContextHolder.setDbType(getDBTypeKey(pjp.getSignature().getName()));
56     }
57
58     private String getDBTypeKey(String methodName) {
59         methodName = methodName.toUpperCase();
60         for (String method : methods.keySet()) {
61             String m = method.toUpperCase();
62             /**
63              * 忽略大小写
64              * method 如果不包含 '*', 则以方法名匹配 method
65              * method 包含 '*', 则匹配以 method 开头, 或者 等于method 的方法
66              */
67             if (!method.contains("*")
68                     && m.equals(methodName)
69                     || methodName
70                     .startsWith(m.substring(0, m.indexOf("*") - 1))
71                     || methodName.equals(m.substring(0, m.indexOf("*") - 1))) {
72                 return methods.get(method);
73             }
74         }
75         return defaultDataSource;
76     }
77
78     //设置AOP执行顺序, 这里设置优于事务
79     @Override
80     public int getOrder() {
81         return 1;
82     }
83 }

View Code

这里有一个小知识点,aop实现类实现了orderd接口,这个接口有一个方法getOrder(),返回aop的执行顺序,就是在同一个切点如果切入了多个aop,则按order从小到大执行,这里我们设置优于事务aop,因为事务是 基于dataSource的,即先切换数据源,在开启事务,否则可能会存在切换了已开启了事务的数据源,导致事务不生效。

步骤四:配置aop切面

 1 <!-- 数据源读写分离  aop -->
 2     <bean id="dynamicDataSourceAOP" class="base.dataSource.aop.DynamicDataSourceAOP">
 3         <property name="methods">
 4              <map>
 5                  <entry key="select*" value="dataSourceKeyR" />
 6                  <entry key="get*" value="dataSourceKeyR" />
 7                  <entry key="find*" value="dataSourceKeyR" />
 8                  <entry key="page*" value="dataSourceKeyR" />
 9                  <entry key="query*" value="dataSourceKeyRW" />
10              </map>
11            </property>
12         <property name="defaultDataSource" value="dataSourceKeyRW"/>
13        </bean>
14
15
16     <aop:config>
17         <!-- 切点 管理所有Service的方法 -->
18         <aop:pointcut
19             expression="execution(* com.b2c.*.service.*Service.*(..))"
20             id="transactionPointCut" />
21         <!-- 进行事务控制 Advisor -->
22         <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointCut" />
23
24         <!-- 动态数据源aop,  aop:advisor配置一定要在  aop:aspect之前,否则报错    -->
25         <aop:aspect ref="dynamicDataSourceAOP">
26             <aop:before method="dynamicDataSource" pointcut-ref="transactionPointCut" />
27         </aop:aspect>
28
29     </aop:config>

View Code

至此全部完成, 另外这只是个人观点,有更好的想法欢迎交流指正。


  

转载于:https://www.cnblogs.com/darkwind/p/5414228.html

spring 动态数据源相关推荐

  1. 我已经把它摸的透透的了!!!Spring 动态数据源设计实践,全面解析

    [ Spring 动态数据源 动态数据源是什么?它能解决什么??? 在实际的开发中,同一个项目中使用多个数据源是很常见的场景.比如,一个读写分离的项目存在主数据源与读数据源. 所谓动态数据源,就是通过 ...

  2. spring 动态数据源切换实例

    我们很多项目中业务都需要涉及到多个数据源,最简单的做法就是直接在java代码里面lookup需要的数据源,但是这样的做法很明显耦合度太高了, 而且当逻辑流程不够严谨的时候就会出现各种大家不愿意看到的问 ...

  3. SPRING动态数据源使用方法

    情况: 项目过程中遇到这样一个需求,系统启动后动态设置数据源,不同用户登录系统后访问的数据库不同. 我们的系统有很多版本,不同版本开发在不同的数据库上,但是系统需要的一些配置依赖于数据库,所以需要有一 ...

  4. Spring 下,关于动态数据源的事务问题的探讨

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:青石路 cnblogs.com/youzhibing ...

  5. 让媳妇瞬间搞懂Spring 多数据源操作(SpringBoot + Durid)

    1.快速理解 Spring 多数据源操作 最近在调研 Spring 如何配置多数据源的操作,结果被媳妇吐槽,整天就坐在那打电脑,啥都不干.于是我灵光一现,跟我媳妇说了一下调研结果,第一版本原话如下: ...

  6. Spring Boot 动态数据源(多数据源自己主动切换)

    本文实现案例场景: 某系统除了须要从自己的主要数据库上读取和管理数据外.另一部分业务涉及到其它多个数据库,要求能够在不论什么方法上能够灵活指定详细要操作的数据库. 为了在开发中以最简单的方法使用,本文 ...

  7. spring 多数据源动态切换

    理解spring动态切换数据源,需要对spring具有一定的了解 工作中经常遇到读写分离,数据源切换的问题,那么以下是本作者实际工作中编写的代码  与大家分享一下! 1.定义注解 DataSource ...

  8. Spring AOP之四:利用AOP实现动态数据源切换

    2019独角兽企业重金招聘Python工程师标准>>> 简介和依赖 项目的前提是安装了MySQL数据库,并且建立了2个数据库一个是master,一个是slave,并且这2个数据库都有 ...

  9. java多个数据库数据进行访问_通过Spring Boot配置动态数据源访问多个数据库的实现代码...

    之前写过一篇博客<Spring+Mybatis+Mysql搭建分布式数据库访问框架>描述如何通过Spring+Mybatis配置动态数据源访问多个数据库.但是之前的方案有一些限制(原博客中 ...

最新文章

  1. 文本分类的基本思想和朴素贝叶斯算法原理
  2. unbutu18.04安装Markdown工具typora
  3. 数据库设计指南(四)保证数据的完整性
  4. nodejs http.get 方法可以 request 不行
  5. 企业网络推广——网站页面布局优化对于企业网络推广来说非同一般
  6. 如何在linux查找虚拟机主机号_Linux主机名如何重命名?
  7. 中国塑料瓶市场趋势报告、技术动态创新及市场预测
  8. 使用sp_cycle_errorlog 命令清除sqlserver数据库错误日志
  9. c lambda表达式 select 改变字段名称_C博客作业01--分支、顺序结构 - 吖黑大帅
  10. 【转】mysql数据库中实现内连接、左连接、右连接
  11. Python 帮助文件
  12. 软件开发的需求文档如何去写
  13. 站在巨人的肩膀上,C++开源库大全
  14. 「图文」介绍下微信怎么拉票刷票及微信投票怎样自己拉票方法
  15. 同时使用 IE7 和 IE6 的方法
  16. 详解三大专利类型之首:发明专利
  17. 七彩虹 pci内存控制器 感叹号 蓝屏 DPC_WATCHDOG_VIOLATION
  18. MRM:基于ISMRM研究与欧洲痴呆研究动脉自旋灌注成像临床应用的补充建议
  19. 护理专业有必要考计算机吗,护理专业考研有前途吗
  20. python无法输出有颜色的字体_Python通过2种方法输出带颜色字体

热门文章

  1. 在汇编程序中调用C函数
  2. 程序员面试系列——选择排序
  3. JDBC基础知识复习
  4. (转)记录一次迁移 wss WebSocket 的事故
  5. 日常生活小技巧 -- Notepad++一次删除带指定关键字的行
  6. mac的截图在linux下打不开,mac版截图软件Snip详细使用教程及常见问题
  7. 长连接/websocket/SSE等主流服务器推送技术比较
  8. Android 7.1.1 锁屏界面启动流程
  9. Android6.0 wakelock深入分析
  10. Android 插件框架实现思路及原理