spring 动态数据源
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 动态数据源相关推荐
- 我已经把它摸的透透的了!!!Spring 动态数据源设计实践,全面解析
[ Spring 动态数据源 动态数据源是什么?它能解决什么??? 在实际的开发中,同一个项目中使用多个数据源是很常见的场景.比如,一个读写分离的项目存在主数据源与读数据源. 所谓动态数据源,就是通过 ...
- spring 动态数据源切换实例
我们很多项目中业务都需要涉及到多个数据源,最简单的做法就是直接在java代码里面lookup需要的数据源,但是这样的做法很明显耦合度太高了, 而且当逻辑流程不够严谨的时候就会出现各种大家不愿意看到的问 ...
- SPRING动态数据源使用方法
情况: 项目过程中遇到这样一个需求,系统启动后动态设置数据源,不同用户登录系统后访问的数据库不同. 我们的系统有很多版本,不同版本开发在不同的数据库上,但是系统需要的一些配置依赖于数据库,所以需要有一 ...
- Spring 下,关于动态数据源的事务问题的探讨
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 作者:青石路 cnblogs.com/youzhibing ...
- 让媳妇瞬间搞懂Spring 多数据源操作(SpringBoot + Durid)
1.快速理解 Spring 多数据源操作 最近在调研 Spring 如何配置多数据源的操作,结果被媳妇吐槽,整天就坐在那打电脑,啥都不干.于是我灵光一现,跟我媳妇说了一下调研结果,第一版本原话如下: ...
- Spring Boot 动态数据源(多数据源自己主动切换)
本文实现案例场景: 某系统除了须要从自己的主要数据库上读取和管理数据外.另一部分业务涉及到其它多个数据库,要求能够在不论什么方法上能够灵活指定详细要操作的数据库. 为了在开发中以最简单的方法使用,本文 ...
- spring 多数据源动态切换
理解spring动态切换数据源,需要对spring具有一定的了解 工作中经常遇到读写分离,数据源切换的问题,那么以下是本作者实际工作中编写的代码 与大家分享一下! 1.定义注解 DataSource ...
- Spring AOP之四:利用AOP实现动态数据源切换
2019独角兽企业重金招聘Python工程师标准>>> 简介和依赖 项目的前提是安装了MySQL数据库,并且建立了2个数据库一个是master,一个是slave,并且这2个数据库都有 ...
- java多个数据库数据进行访问_通过Spring Boot配置动态数据源访问多个数据库的实现代码...
之前写过一篇博客<Spring+Mybatis+Mysql搭建分布式数据库访问框架>描述如何通过Spring+Mybatis配置动态数据源访问多个数据库.但是之前的方案有一些限制(原博客中 ...
最新文章
- 文本分类的基本思想和朴素贝叶斯算法原理
- unbutu18.04安装Markdown工具typora
- 数据库设计指南(四)保证数据的完整性
- nodejs http.get 方法可以 request 不行
- 企业网络推广——网站页面布局优化对于企业网络推广来说非同一般
- 如何在linux查找虚拟机主机号_Linux主机名如何重命名?
- 中国塑料瓶市场趋势报告、技术动态创新及市场预测
- 使用sp_cycle_errorlog 命令清除sqlserver数据库错误日志
- c lambda表达式 select 改变字段名称_C博客作业01--分支、顺序结构 - 吖黑大帅
- 【转】mysql数据库中实现内连接、左连接、右连接
- Python 帮助文件
- 软件开发的需求文档如何去写
- 站在巨人的肩膀上,C++开源库大全
- 「图文」介绍下微信怎么拉票刷票及微信投票怎样自己拉票方法
- 同时使用 IE7 和 IE6 的方法
- 详解三大专利类型之首:发明专利
- 七彩虹 pci内存控制器 感叹号 蓝屏 DPC_WATCHDOG_VIOLATION
- MRM:基于ISMRM研究与欧洲痴呆研究动脉自旋灌注成像临床应用的补充建议
- 护理专业有必要考计算机吗,护理专业考研有前途吗
- python无法输出有颜色的字体_Python通过2种方法输出带颜色字体