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
<!-- 引入属性文件,方便配置内容修改 -->
<context:property-placeholder location="classpath:jdbc.properties" /> <!-- 数据库链接(主库) -->
<bean id="dataSourceRW" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc_url}" />
<property name="username" value="${jdbc_username}" />
<property name="password" value="${jdbc_password}" /> <!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${druid_initialSize}" />
<property name="minIdle" value="${druid_minIdle}" />
<property name="maxActive" value="${druid_maxActive}" /> <!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${druid_maxWait}" /> <property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" /> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="100" /> <!-- 密码加密 -->
<property name="filters" value="config" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean> <!-- 数据库链接(只读库) -->
<bean id="dataSourceR" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<!-- 基本属性 url、user、password -->
<property name="url" value="${jdbc_url_read}" />
<property name="username" value="${jdbc_username_read}" />
<property name="password" value="${jdbc_password_read}" /> <!-- 配置初始化大小、最小、最大 -->
<property name="initialSize" value="${druid_initialSize}" />
<property name="minIdle" value="${druid_minIdle}" />
<property name="maxActive" value="${druid_maxActive}" /> <!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="${druid_maxWait}" /> <property name="validationQuery" value="SELECT 'x'" />
<property name="testWhileIdle" value="true" /> <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name="poolPreparedStatements" value="true" />
<property name="maxPoolPreparedStatementPerConnectionSize"
value="100" /> <!-- 密码加密 -->
<property name="filters" value="config" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean>
步骤二:配置 DynamicDataSource
<!-- 动态数据源 -->
<bean id="dynamicDataSource" class="base.dataSource.DynamicDataSource">
<!-- 通过key-value关联数据源 -->
<property name="targetDataSources">
<map>
<entry value-ref="dataSourceRW" key="dataSourceRW"></entry>
<entry value-ref="dataSourceR" key="dataSourceR"></entry>
</map>
</property>
<!-- 默认的DataSource配置-->
<property name="defaultTargetDataSource" ref="dataSourceR" />
</bean>
package base.dataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource{ @Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.getDbType();
}
}
DynamicDataSource 继承了spring 的 AbstractRoutingDataSource 抽象类 实现determineCurrentLookupKey()方法
determineCurrentLookupKey()方法在 SessionFactory 获取 DataSoure时被调用,AbstractRoutingDataSource 代码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
// package org.springframework.jdbc.datasource.lookup; import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.util.Assert; 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; public AbstractRoutingDataSource() {
} public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
} public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
} public void setLenientFallback(boolean lenientFallback) {
this.lenientFallback = lenientFallback;
} public void setDataSourceLookup(DataSourceLookup dataSourceLookup) {
this.dataSourceLookup = (DataSourceLookup)(dataSourceLookup != null?dataSourceLookup:new JndiDataSourceLookup());
} public void afterPropertiesSet() {
if(this.targetDataSources == null) {
throw new IllegalArgumentException("Property \'targetDataSources\' is required");
} else {
this.resolvedDataSources = new HashMap(this.targetDataSources.size());
Iterator var1 = this.targetDataSources.entrySet().iterator(); while(var1.hasNext()) {
Entry entry = (Entry)var1.next();
Object lookupKey = this.resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = this.resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
} if(this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
} }
} protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
} protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if(dataSource instanceof DataSource) {
return (DataSource)dataSource;
} else if(dataSource instanceof String) {
return this.dataSourceLookup.getDataSource((String)dataSource);
} else {
throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
} public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
} public Connection getConnection(String username, String password) throws SQLException {
return this.determineTargetDataSource().getConnection(username, password);
} public <T> T unwrap(Class<T> iface) throws SQLException {
return iface.isInstance(this)?this:this.determineTargetDataSource().unwrap(iface);
} public boolean isWrapperFor(Class<?> iface) throws SQLException {
return iface.isInstance(this) || this.determineTargetDataSource().isWrapperFor(iface);
} protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = this.determineCurrentLookupKey();
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;
}
} protected abstract Object determineCurrentLookupKey();
}
AbstractRoutingDataSource 两个主要变量:
targetDataSources 初始化了 DataSource 的map集合, defaultTargetDataSource 初始化默认的DataSource 并实现了 DataSource的 getConnection() 获取数据库连接的方法,该方法从determineTargetDataSource()获取 DataSource, determineTargetDataSource() 调用了我们 DynamicDataSource 中实现的 determineCurrentLookupKey() 方法获取DataSource(determineCurrentLookupKey()方法返回的只是我们初始化的DataSource Map集合key值, 通过key获取DataSource的方法这里不做赘述,感兴趣自己研究下),
determineTargetDataSource()的主要逻辑是获取我们切换的DataSource, 如果没有的话读取默认的DataSource。 在DynamicDataSource中我们定义了一个线程变量DBContextHolder来存放我们切换的DataSource, 防止其它线程覆盖我们的DataSource。
package base.dataSource; /**
*
* @author xiao
* @date 下午3:27:52
*/
public final class DBContextHolder { /**
* 线程threadlocal
*/
private static ThreadLocal<String> contextHolder = new ThreadLocal<>(); private static String DEFAUL_DB_TYPE_RW = "dataSourceKeyRW"; /**
* 获取本线程的dbtype
* @return
*/
public static String getDbType() {
String db = contextHolder.get();
if (db == null) {
db = DEFAUL_DB_TYPE_RW;// 默认是读写库
}
return db;
} /**
*
* 设置本线程的dbtype
*
* @param str
*/
public static void setDbType(String str) {
contextHolder.set(str);
} /**
* clearDBType
*
* @Title: clearDBType
* @Description: 清理连接类型
*/
public static void clearDBType() {
contextHolder.remove();
}
}
至此我们获取DataSource的逻辑已完成, 接下来我们要考虑 设置DataSource, 即为DBContextHolder, set值。我们在代码中调用DBContextHolder.set()来设置DataSource,理论上可以在代码的任何位置设置, 不过为了统一规范,我们通过aop来实现,此时我们面临的问题,在哪一层切入, 方案一: 在dao层切入,dao封装了数据库的CRUD,在这一层切入控制最灵活,但是我们一般在service业务层切入事务,如果在dao层切换数据源,会遇到事务无法同步的问题,虽然有分布式事务机制,但是目前成熟的框架很难用,如果使用过 就会知道分布式事务是一件非常恶心的事情,而且分布式事务本就不是一个好的选择。方案二: 在service业务层切入,可以避免事务问题,但也相对影响了数据源切换的灵活性,这里要根据实际情况灵活选择,我们采用的在service业务层切入,具体实现如下:
步骤三:实现aop
package base.dataSource.aop; import java.util.Map; import org.aspectj.lang.JoinPoint;
import org.springframework.core.Ordered; import base.dataSource.DBContextHolder; /**
* 动态数据源切换aop
* @author xiao
* @date 2015年7月23日下午4:17:13
*/
public final class DynamicDataSourceAOP implements Ordered{ /**
* 方法, 数据源应映射规则map
*/
Map<String, String> methods; /**
* 默认数据源
*/
String defaultDataSource; public String getDefaultDataSource() {
return defaultDataSource;
} public void setDefaultDataSource(String defaultDataSource) {
if(null == defaultDataSource || "".equals(defaultDataSource)){
throw new NullPointerException("defaultDataSource Must have a default value");
}
this.defaultDataSource = defaultDataSource;
} public Map<String, String> getMethods() {
return methods;
} public void setMethods(Map<String, String> methods) {
this.methods = methods;
} /**
* before 数据源切换
*
* @param pjp
* @throws Throwable
*/
public void dynamicDataSource(JoinPoint pjp) throws Throwable {
DBContextHolder.setDbType(getDBTypeKey(pjp.getSignature().getName()));
} private String getDBTypeKey(String methodName) {
methodName = methodName.toUpperCase();
for (String method : methods.keySet()) {
String m = method.toUpperCase();
/**
* 忽略大小写
* method 如果不包含 '*', 则以方法名匹配 method
* method 包含 '*', 则匹配以 method 开头, 或者 等于method 的方法
*/
if (!method.contains("*")
&& m.equals(methodName)
|| methodName
.startsWith(m.substring(0, m.indexOf("*") - 1))
|| methodName.equals(m.substring(0, m.indexOf("*") - 1))) {
return methods.get(method);
}
}
return defaultDataSource;
} //设置AOP执行顺序, 这里设置优于事务
@Override
public int getOrder() {
return 1;
}
}
这里有一个小知识点,aop实现类实现了orderd接口,这个接口有一个方法getOrder(),返回aop的执行顺序,就是在同一个切点如果切入了多个aop,则按order从小到大执行,这里我们设置优于事务aop,因为事务是 基于dataSource的,即先切换数据源,在开启事务,否则可能会存在切换了已开启了事务的数据源,导致事务不生效。
步骤四:配置aop切面
<!-- 数据源读写分离 aop -->
<bean id="dynamicDataSourceAOP" class="base.dataSource.aop.DynamicDataSourceAOP">
<property name="methods">
<map>
<entry key="select*" value="dataSourceKeyR" />
<entry key="get*" value="dataSourceKeyR" />
<entry key="find*" value="dataSourceKeyR" />
<entry key="page*" value="dataSourceKeyR" />
<entry key="query*" value="dataSourceKeyRW" />
</map>
</property>
<property name="defaultDataSource" value="dataSourceKeyRW"/>
</bean> <aop:config>
<!-- 切点 管理所有Service的方法 -->
<aop:pointcut
expression="execution(* com.b2c.*.service.*Service.*(..))"
id="transactionPointCut" />
<!-- 进行事务控制 Advisor -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointCut" /> <!-- 动态数据源aop, aop:advisor配置一定要在 aop:aspect之前,否则报错 -->
<aop:aspect ref="dynamicDataSourceAOP">
<aop:before method="dynamicDataSource" pointcut-ref="transactionPointCut" />
</aop:aspect> </aop:config>
至此全部完成, 另外这只是个人观点,有更好的想法欢迎交流指正。
spring 动态数据源的更多相关文章
- Spring动态数据源的配置
Spring动态数据源 我们很多项目中业务都需要涉及到多个数据源,就是对不同的方法或者不同的包使用不同的数据源.最简单的做法就是直接在Java代码里面lookup需要的数据源,但是这种做法耦合性太高, ...
- Spring动态数据源实现读写分离
一.创建基于ThreadLocal的动态数据源容器,保证数据源的线程安全性 package com.bounter.mybatis.extension; /** * 基于ThreadLocal实现的动 ...
- Spring动态数据源-AbstractRoutingDataSource
在分库分表的情况下,在执行SQL时选择连接不同的数据源(库)的思路:配置多个数据源加到动态数据源对象中,根据实际的情况动态切换到相应的数据源中. 如存放订单信息的有10个库,每个库中有100张表,根据 ...
- spring动态数据源+事务
今天在尝试配置spring的动态数据源和事务管理的时候,遇到了几处配置上的问题,在此记录下: 1.使用了spring的aop思想,实现了动态数据源的切换. 2.spring的事务管理,是基于数据源的, ...
- 【sping揭秘】21、Spring动态数据源的切换
对于多个数据源的时候,我们如何切换不同的数据源进行数据库的操作呢? 当然我们可以直接定义2个DataSource,然后在每次获取connection的时候,从不同的DataSource中获取conne ...
- Spring 下,关于动态数据源的事务问题的探讨
开心一刻 毒蛇和蟒蛇在讨论谁的捕猎方式最高效. 毒蛇:我只需要咬对方一口,一段时间内它就会逐渐丧失行动能力,最后死亡. 蟒蛇冷笑:那还得等生效时间,我只需要缠住对方,就能立刻致它于死地. 毒蛇大怒:你 ...
- Spring 注解动态数据源设计实践
Spring 动态数据源 动态数据源是什么?解决了什么问题? 在实际的开发中,同一个项目中使用多个数据源是很常见的场景.比如,一个读写分离的项目存在主数据源与读数据源. 所谓动态数据源,就是通过Spr ...
- Spring 的动态数据源实现
1. 配置多个数据源 这里以两个c3p0数据库连接池的数据源作为实例.在Spring框架下使用c3p0的数据库需要加入c3p0-0.9.1.2.jar(现在最新的)这个支持包.这里以数据同步项目为例: ...
- Spring动态配置多数据源
Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性.而这样的方案就会不同于常见的单一数据实例的方案,这就要程序在运行时根据当时 ...
随机推荐
- 浮动闭合方案:clearfix
1 ;clear:both;visibility:hidden} .clearfix{*+height:1%;} 2 .clearfix{overflow:auto;_height:1%} 3 ;}
- Java Post 数据请求和接收
这两天在做http服务端请求操作,客户端post数据到服务端后,服务端通过request.getParameter()进行请求,无法读取到数据,搜索了一下发现是因为设置为text/plain模式才导致 ...
- centos7安装chrome的历程(fedora同)
安装 首先是下载,地址奉上:http://www.google.cn/chrome/browser/desktop/index.html,选择64 bit .rpm (适用于 Fedora/openS ...
- MDA系统分析实战--图书馆管理系统
MDA系统分析实战--图书馆管理系统 本文通过MDA系统分析方法,对图书馆管理系统进行分析,简要叙述系统分析的一般过程.首先,简要介绍什么是MDA:MDA(Model-Driven Architect ...
- U3D 精灵的点击监听
U3D游戏中,可能会用到点击对象,完成某项操作, 方法一:可以通过接收Input对象的输入,进行利用 方法二:给对象绑定一个collier 组件,然后就能后使用内置方法 这里有点不同,方法一,是不管哪 ...
- Spring中事务的5种属性总结
Sping的事务 和 数据库的事务是不同的概念,数据库的事务一般称为底层事务 Spring的事务是对这种事务的抽象 我称之为逻辑事务 Spring对事务的功能进行了扩展,除了基本的Isolation之 ...
- [上传下载] C#修改DownLoadHelper上传下载帮助类 (转载)
点击下载 DownLoadHelper.rar 主要功能如下 /// <summary> /// 输出硬盘文件,提供下载 支持大文件.续传.速度限制.资源占用小 /// </summ ...
- Js 的常用方法:页面跳转,Session,类继承
MyApp.Base = function () { } var basePrototype = MyApp.Base["prototype"]; //对象克隆方法 basePro ...
- 15第十五章UDF用户自定义函数(转载)
15第十五章UDF用户自定义函数 待补上 原文链接 本文由豆约翰博客备份专家远程一键发布
- [Twisted] transport
transport代表网络上两个节点的连接.它描述了连接的具体细节,如TCP还是UDP. transports实现了ITransport接口,包含以下方法 write:以非阻塞的方式向连接写数据. w ...