原文:https://blog.csdn.net/u012881904/article/details/77449710
AbstractRoutingDataSource動態(tài)數(shù)據(jù)源切換
上周末,室友通宵達旦的敲代碼處理他的多數(shù)據(jù)源的問題回窘,搞的非常的緊張,也和我聊了聊天晨横,大概的了解了他的業(yè)務(wù)的需求。一般的情況下我們都是使用SSH或者SSM框架進行處理我們的數(shù)據(jù)源的信息箫柳。
操作數(shù)據(jù)一般都是在DAO層進行處理手形,可以選擇直接使用JDBC進行編程(http://blog.csdn.net/yanzi1225627/article/details/26950615/)
或者是使用多個DataSource 然后創(chuàng)建多個SessionFactory,在使用Dao層的時候通過不同的SessionFactory進行處理悯恍,不過這樣的入侵性比較明顯库糠,一般的情況下我們都是使用繼承HibernateSupportDao進行封裝了的處理,如果多個SessionFactory這樣處理就是比較的麻煩了涮毫,修改的地方估計也是蠻多的
最后一個瞬欧,也就是使用AbstractRoutingDataSource的實現(xiàn)類通過AOP或者手動處理實現(xiàn)動態(tài)的使用我們的數(shù)據(jù)源贷屎,這樣的入侵性較低,非常好的滿足使用的需求艘虎。比如我們希望對于讀寫分離或者其他的數(shù)據(jù)同步的業(yè)務(wù)場景
單數(shù)據(jù)源的場景(一般的Web項目工程這樣配置進行處理豫尽,就已經(jīng)比較能夠滿足我們的業(yè)務(wù)需求)
多數(shù)據(jù)源多SessionFactory這樣的場景,估計作為剛剛開始想象想處理在使用框架的情況下處理業(yè)務(wù)顷帖,配置多個SessionFactory,然后在Dao層中對于特定的請求渤滞,通過特定的SessionFactory即可處理實現(xiàn)這樣的業(yè)務(wù)需求贬墩,不過這樣的處理帶來了很多的不便之處,所有很多情況下我們寧愿直接使用封裝的JDBC編程妄呕,或者使用Mybatis處理這樣的業(yè)務(wù)場景
使用AbstractRoutingDataSource 的實現(xiàn)類陶舞,進行靈活的切換,可以通過AOP或者手動編程設(shè)置當(dāng)前的DataSource绪励,不用修改我們編寫的對于繼承HibernateSupportDao的實現(xiàn)類的修改肿孵,這樣的編寫方式比較好,至于其中的實現(xiàn)原理疏魏,讓我細細到來停做。我們想看看如何去應(yīng)用,實現(xiàn)原理慢慢的說大莫!
編寫AbstractRoutingDataSource的實現(xiàn)類,HandlerDataSource就是提供給我們動態(tài)選擇數(shù)據(jù)源的數(shù)據(jù)的信息蛉腌,我們這里編寫一個根據(jù)當(dāng)前線程來選擇數(shù)據(jù)源,然后通過AOP攔截特定的注解只厘,設(shè)置當(dāng)前的數(shù)據(jù)源信息烙丛,也可以手動的設(shè)置當(dāng)前的數(shù)據(jù)源,在編程的類中羔味。
package com.common.utils.manydatasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* descrption: 多數(shù)據(jù)源的選擇
* authohr: wangji
* date: 2017-08-21 10:32
*/
public class MultipleDataSourceToChoose extends AbstractRoutingDataSource {
/**
* @desction: 根據(jù)Key獲取數(shù)據(jù)源的信息河咽,上層抽象函數(shù)的鉤子
* @author: wangji
* @date: 2017/8/21
* @param:
* @return:
*/
@Override
protected Object determineCurrentLookupKey() {
return HandlerDataSource.getDataSource();
}
}
設(shè)置動態(tài)選擇的Datasource,這里的Set方法可以留給AOP調(diào)用赋元,或者留給我們的具體的Dao層或者Service層中手動調(diào)用忘蟹,在執(zhí)行SQL語句之前。
package com.common.utils.manydatasource;
/**
* descrption: 根據(jù)當(dāng)前線程來選擇具體的數(shù)據(jù)源
* authohr: wangji
* date: 2017-08-21 10:36
*/
public class HandlerDataSource {
private static ThreadLocal<String> handlerThredLocal = new ThreadLocal<String>();
/**
* @desction: 提供給AOP去設(shè)置當(dāng)前的線程的數(shù)據(jù)源的信息
* @author: wangji
* @date: 2017/8/21
* @param: [datasource]
* @return: void
*/
public static void putDataSource(String datasource) {
handlerThredLocal.set(datasource);
}
/**
* @desction: 提供給AbstractRoutingDataSource的實現(xiàn)類们陆,通過key選擇數(shù)據(jù)源
* @author: wangji
* @date: 2017/8/21
* @param: []
* @return: java.lang.String
*/
public static String getDataSource() {
return handlerThredLocal.get();
}
/**
* @desction: 使用默認的數(shù)據(jù)源
*/
public static void clear() {
handlerThredLocal.remove();
}
}
設(shè)置攔截數(shù)據(jù)源的注解寒瓦,可以設(shè)置在具體的類上,或者在具體的方法上坪仇,dataSource是當(dāng)前數(shù)據(jù)源的一個別名用于標(biāo)識我們的數(shù)據(jù)源的信息杂腰。
package com.common.utils.manydatasource;
import java.lang.annotation.*;
/**
* @description: 創(chuàng)建攔截設(shè)置數(shù)據(jù)源的注解
* Created by wangji on 2017/8/21.
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicSwitchDataSource {
String dataSource() default "";
}
AOP攔截類的實現(xiàn),通過攔截上面的注解椅文,在其執(zhí)行之前處理設(shè)置當(dāng)前執(zhí)行SQL的數(shù)據(jù)源的信息喂很,HandlerDataSource.putDataSource(….),這里的數(shù)據(jù)源信息從我們設(shè)置的注解上面獲取信息惜颇,如果沒有設(shè)置就是用默認的數(shù)據(jù)源的信息。
package com.common.utils.manydatasource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* descrption: 使用AOP攔截特定的注解去動態(tài)的切換數(shù)據(jù)源
* authohr: wangji
* date: 2017-08-21 10:42
*/
@Aspect
@Slf4j
@Component
@Order(1)
public class HandlerDataSourceAop {
//@within在類上設(shè)置
//@annotation在方法上進行設(shè)置
@Pointcut("@within(com.common.utils.manydatasource.DynamicSwitchDataSource)||@annotation(com.common.utils.manydatasource.DynamicSwitchDataSource)")
public void pointcut() {}
@Before("pointcut()")
public void doBefore(JoinPoint joinPoint)
{
Method method = ((MethodSignature)joinPoint.getSignature()).getMethod();
DynamicSwitchDataSource annotationClass = method.getAnnotation(DynamicSwitchDataSource.class);//獲取方法上的注解
if(annotationClass == null){
annotationClass = joinPoint.getTarget().getClass().getAnnotation(DynamicSwitchDataSource.class);//獲取類上面的注解
if(annotationClass == null) return;
}
//獲取注解上的數(shù)據(jù)源的值的信息
String dataSourceKey = annotationClass.dataSource();
if(dataSourceKey !=null){
//給當(dāng)前的執(zhí)行SQL的操作設(shè)置特殊的數(shù)據(jù)源的信息
HandlerDataSource.putDataSource(dataSourceKey);
}
log.info("AOP動態(tài)切換數(shù)據(jù)源少辣,className"+joinPoint.getTarget().getClass().getName()+"methodName"+method.getName()+";dataSourceKey:"+dataSourceKey==""?"默認數(shù)據(jù)源":dataSourceKey);
}
@After("pointcut()")
public void after(JoinPoint point) {
//清理掉當(dāng)前設(shè)置的數(shù)據(jù)源凌摄,讓默認的數(shù)據(jù)源不受影響
HandlerDataSource.clear();
}
}
配置數(shù)據(jù)源在Spring 核心容器中配置
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis
jdbc.username=root
jdbc.password=root
jdbc2.url=jdbc:mysql://127.0.0.1:3306/datasource2
<!-- 配置數(shù)據(jù)源 -->
<bean id="dataSource0" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="10"/>
</bean>
<bean id="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" init-method="init">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc2.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="10"/>
</bean>
配置之前我們實現(xiàn)的數(shù)據(jù)源選擇的中間層AbstractRoutingDataSource的實現(xiàn)類,這里的key就是數(shù)據(jù)源信息的別名漓帅,通過這個key可以選擇到數(shù)據(jù)源的信息锨亏。MultipleDataSourceToChoose就是上面寫的數(shù)據(jù)源選擇器的實現(xiàn)類
bean id="dataSource" class="com.common.utils.manydatasource.MultipleDataSourceToChoose" lazy-init="true">
<description>數(shù)據(jù)源</description>
<property name="targetDataSources">
<map key-type="java.lang.String" value-type="javax.sql.DataSource">
<entry key="datasource0" value-ref="dataSource0" />
<entry key="datasource1" value-ref="dataSource1" />
</map>
</property>
<!-- 設(shè)置默認的目標(biāo)數(shù)據(jù)源 -->
<property name="defaultTargetDataSource" ref="dataSource0" />
</bean>
SessionFactory的配置還是照舊,使用以前的配置忙干,只不過當(dāng)前選擇的數(shù)據(jù)源是datasource,也就是數(shù)據(jù)源選擇的中間層MultipleDataSourceToChoose器予,因為當(dāng)前的中間層中實現(xiàn)了DataSource這個接口,所以可以看做為DataSource的是實現(xiàn)類啦捐迫,所以配置不會出現(xiàn)問題乾翔。
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--指定Hibernate屬性 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.format_sql">false</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.autoReconnect">true</prop>
<prop key="hibernate.jdbc.batch_size">50</prop>
<prop key="hibernate.connection.autocommit">false</prop>
<prop key="hibernate.connection.release_mode">after_transaction</prop>
<prop key="hibernate.bytecode.use_reflection_optimizer">false</prop>
</props>
</property>
<property name="packagesToScan">
<list>
<value>com.module</value>
</list>
</property>
</bean>
簡單的使用AOP進行測試一下,這里測試的結(jié)果時不同的施戴,所以是生效的反浓,使用了不同的數(shù)據(jù)源,但是底層的實現(xiàn)沒有進行任何的修改處理赞哗。
@Service
@Slf4j
public class UserInfoService implements IUserInfoService {
@Resource
private UserDao userDao;
@Autowired
private CommonHibernateDao commonDao;
@TestValidateParam
public User getUserInfoById(Integer id) {
return userDao.findById(id);
}
@DynamicSwitchDataSource(dataSource = "datasource0")
public void save(User user) {
userDao.save(user);
}
@DynamicSwitchDataSource(dataSource = "datasource1")
public List<User> findAll(){
String sql = "select u.userName as name,u.userAge as age,u.userAddress as address,u.id from user u";
List<User> list =commonDao.findListBySQL(sql,User.class);
return list;
}
}
也可以不適用AOP雷则,直接在編程中實現(xiàn),通過測試肪笋,結(jié)果分別為兩個數(shù)據(jù)庫中的信息
public void test(){
HandlerDataSource.putDataSource("datasource1");
String sql = "select u.userName as name,u.userAge as age,u.userAddress as address,u.id from user u";
List<User> list =commonDao.findListBySQL(sql,User.class);
HandlerDataSource.putDataSource("datasource0");
commonDao.deleteById("2",User.class);
}
實現(xiàn)原理,MultipleDataSourceToChoose的繼承結(jié)構(gòu)圖巧婶,之前說過他是DataSource的子類,由于無論我們是使用Mybatis還是使用Hibernate進行SQL操作的時候總會執(zhí)行g(shù)etConnection()涂乌,無論我們的數(shù)據(jù)源是否使用了數(shù)據(jù)庫連接池艺栈,因為數(shù)據(jù)庫連接池的主要作用就是保持一堆的Connection不進行關(guān)閉的處理,節(jié)省我們的關(guān)閉和打開連接的開銷湾盒。http://blog.csdn.net/shuaihj/article/details/14223015/ 淺談數(shù)據(jù)庫連接池說的簡單易懂湿右。 Connection getConnection() throws SQLException;所以這句話總是要執(zhí)行的,只是AbstractRoutingDataSource這個類給我們進行了一些中介的處理罚勾,在獲取Connection的時候會去尋找保存的DataSource的引用毅人,到底是選擇哪個DataSource進行處理,看代碼尖殃!
配置的參數(shù)
<bean id="dataSource" class="com.common.utils.manydatasource.MultipleDataSourceToChoose" lazy-init="true">
<description>數(shù)據(jù)源</description>
<property name="targetDataSources">
<map key-type="java.lang.String" value-type="javax.sql.DataSource">
<entry key="datasource0" value-ref="dataSource0" />
<entry key="datasource1" value-ref="dataSource1" />
</map>
</property>
<!-- 設(shè)置默認的目標(biāo)數(shù)據(jù)源 -->
<property name="defaultTargetDataSource" ref="dataSource0" />
</bean>
targetDataSources丈莺,是一個Map對于數(shù)據(jù)源的引用
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
對于實現(xiàn)SQL的Connection getConnection() throws SQLException的實現(xiàn),其實就是代理模式找到之前Map的引用送丰,通過key缔俄,而這個key就是我們靈活配置的key,通過這個key就可以尋找到這個值。
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
這里說的非常的詳細俐载,通過鉤子函數(shù)讓子類去實現(xiàn)蟹略,尋找特定的key,然后選擇DataSource 的時候就可以很靈活的使用啦遏佣!
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
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 + "]");
}
return dataSource;
}
這個就是模板方法模式中常見的鉤子函數(shù)挖炬,在HttpServlet中也有類似的使用鉤子,非常的棒状婶,不過這個是必須實現(xiàn)意敛,httpServlet不是必須實現(xiàn),只是添加一些補充膛虫。由于每次執(zhí)行數(shù)據(jù)庫的調(diào)用空闲,總會執(zhí)行這個getConnection方法,每次都查看AOP中是否設(shè)置了當(dāng)前的數(shù)據(jù)源走敌,然后找到Map的引用的代理的數(shù)據(jù)源的Connection方法,原理沒有變化的逗噩。
/**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
protected abstract Object determineCurrentLookupKey();
這里就是我們的實現(xiàn)的數(shù)據(jù)源的選擇哦掉丽!
/**
* descrption: 多數(shù)據(jù)源的選擇
* authohr: wangji
* date: 2017-08-21 10:32
*/
public class MultipleDataSourceToChoose extends AbstractRoutingDataSource {
/**
* @desction: 根據(jù)Key獲取數(shù)據(jù)源的信息,上層抽象函數(shù)的鉤子
* @author: wangji
* @date: 2017/8/21
* @param:
* @return:
*/
@Override
protected Object determineCurrentLookupKey() {
return HandlerDataSource.getDataSource();
}
}
但是這個由于有多個數(shù)據(jù)源導(dǎo)致我們只能管理默認的數(shù)據(jù)源的事務(wù)异雁!
http://blog.csdn.net/erixhao/article/details/52133153/
https://github.com/WangJi92/mybatits-study/blob/master/mybatis-study/study-7-spring-Hibernate%20-manydatasource/src/main/java/com/common/utils/manydatasource/MultipleDataSourceToChoose.java/ 源碼地址
同類文章
SpringBoot + MyBatis + MySQL 讀寫分離實戰(zhàn)
http://www.reibang.com/p/8904af2c029a
AOP實現(xiàn)動態(tài)數(shù)據(jù)源切換 AbstractRoutingDataSource
http://www.reibang.com/p/cd99b94fe9de
spingboot 中通過 DynamicDataSource來動態(tài)獲取數(shù)據(jù)源
http://www.reibang.com/p/b2f818b742a2