這篇文章就直接引用AbstractRoutingDataSource動態(tài)數(shù)據(jù)源切換:https://blog.csdn.net/u012881904/article/details/77449710,望博主看到多包涵并巍,我已經(jīng)給了原文地址啦妥箕!
操作數(shù)據(jù)一般都是在DAO層進行處理,可以選擇直接使用JDBC進行編程
或者是使用多個DataSource 然后創(chuàng)建多個SessionFactory毅访,在使用Dao層的時候通過不同的SessionFactory進行處理掠剑,不過這樣的入侵性比較明顯屈芜,一般的情況下我們都是使用繼承HibernateSupportDao進行封裝了的處理,如果多個SessionFactory這樣處理就是比較的麻煩了朴译,修改的地方估計也是蠻多的.
最后一個井佑,也就是使用AbstractRoutingDataSource的實現(xiàn)類通過AOP或者手動處理實現(xiàn)動態(tài)的使用我們的數(shù)據(jù)源,這樣的入侵性較低眠寿,非常好的滿足使用的需求躬翁。比如我們希望對于讀寫分離或者其他的數(shù)據(jù)同步的業(yè)務(wù)場景.
下面是對操作多數(shù)據(jù)庫的幾種方式:
方式1:
graph LR
Datasource-->SessionFactory
SessionFactory-->Dao層實現(xiàn)類
- 單數(shù)據(jù)源的場景(一般的Web項目工程這樣配置進行處理,就已經(jīng)比較能夠滿足我們的業(yè)務(wù)需求)
方式2:
graph LR
Datasource1-->SessionFactory1
Datasource2-->SessionFactory2
SessionFactory1-->Dao層實現(xiàn)類
SessionFactory2-->Dao層實現(xiàn)類
- 多數(shù)據(jù)源多SessionFactory這樣的場景盯拱,估計作為剛剛開始想象想處理在使用框架的情況下處理業(yè)務(wù)盒发,配置多個SessionFactory,然后在Dao層中對于特定的請求狡逢,通過特定的SessionFactory即可處理實現(xiàn)這樣的業(yè)務(wù)需求宁舰,不過這樣的處理帶來了很多的不便之處,所有很多情況下我們寧愿直接使用封裝的JDBC編程奢浑,或者使用Mybatis處理這樣的業(yè)務(wù)場景
方式3:
graph LR
Datasource1-->AbstractRoutingDataSource每執(zhí)行一次SQL操作就動態(tài)選擇DataSource
Datasource2-->AbstractRoutingDataSource每執(zhí)行一次SQL操作就動態(tài)選擇DataSource
AbstractRoutingDataSource每執(zhí)行一次SQL操作就動態(tài)選擇DataSource-->SessionFactory
SessionFactory-->Dao層實現(xiàn)類
- 使用AbstractRoutingDataSource 的實現(xiàn)類蛮艰,進行靈活的切換,可以通過AOP或者手動編程設(shè)置當(dāng)前的DataSource雀彼,不用修改我們編寫的對于繼承HibernateSupportDao的實現(xiàn)類的修改壤蚜,這樣的編寫方式比較好,至于其中的實現(xiàn)原理详羡,讓我細細到來仍律。我們想看看如何去應(yīng)用,實現(xiàn)原理慢慢的說实柠!
我們接下來就來詳細講解方式3啦:
接下來我們需要做的就是:編寫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ù)源,在編程的類中蟹漓。
AbstractRoutingDataSource實現(xiàn)類:
public class MultipleDataSourceToChoose extends AbstractRoutingDataSource {
/**
* @desction: 根據(jù)Key獲取數(shù)據(jù)源的信息炕横,上層抽象函數(shù)的鉤子
* @param:
* @return:
*/
@Override
protected Object determineCurrentLookupKey() {
return HandlerDataSource.getDataSource();
}
}
HandlerDataSource:代碼是自己編寫的一個動態(tài)選擇數(shù)據(jù)源大道一個類
/**
* descrption: 根據(jù)當(dāng)前線程來選擇具體的數(shù)據(jù)源
*/
public class HandlerDataSource {
private static ThreadLocal<String> handlerThredLocal = new ThreadLocal<String>();
/**
* @desction: 提供給AOP去設(shè)置當(dāng)前的線程的數(shù)據(jù)源的信息
* @param: [datasource]
* @return: void
*/
public static void putDataSource(String datasource) {
handlerThredLocal.set(datasource);
}
/**
* @desction: 提供給AbstractRoutingDataSource的實現(xiàn)類,通過key選擇數(shù)據(jù)源
* @param: []
* @return: java.lang.String
*/
public static String getDataSource() {
return handlerThredLocal.get();
}
/**
* @desction: 使用默認(rèn)的數(shù)據(jù)源
*/
public static void clear() {
handlerThredLocal.remove();
}
}
接下來就有兩種方案:
1.手動在程序中設(shè)置數(shù)據(jù)源葡粒。也就是通過手動調(diào)用HandlerDataSource中的putDataSource方法份殿。
2.通過AOP編程(前置通知和后置通知來對我們需要更換數(shù)據(jù)源的方法進行代理增強膜钓,通過注解的方式辨別切換到哪個數(shù)據(jù)源)
我們這里就使用第二種方式,定義自定義注解:如果使用該注解的類或方法卿嘲,就可以在在AOP編程的時候來判斷注解的存在颂斜,從而來改變數(shù)據(jù)源。
/**
* @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 "";
}
我們已經(jīng)定義了注解來判斷切換哪個數(shù)據(jù)源拾枣,那么接下來沃疮,我們需要定義切面類,也就是增強類來對需要代理的類進行增強(也就是說對需要增強的類進行數(shù)據(jù)源的切換梅肤,至于切換哪個數(shù)據(jù)源司蔬,就通過掃描注解來確定)
/**
* descrption: 使用AOP攔截特定的注解去動態(tài)的切換數(shù)據(jù)源
*/
@Aspect
@Component
public class HandlerDataSourceAop {
@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==""?"默認(rèn)數(shù)據(jù)源":dataSourceKey);
}
@After("pointcut()")
public void after(JoinPoint point) {
//清理掉當(dāng)前設(shè)置的數(shù)據(jù)源姨蝴,讓默認(rèn)的數(shù)據(jù)源不受影響
HandlerDataSource.clear();
}
}
上面我們已經(jīng)對數(shù)據(jù)源切換的AOP編程代碼進行了實現(xiàn)啦俊啼,接下來,我們就需要進行配置文件的配置啦似扔!
配置文件需要配置的項有:數(shù)據(jù)源(如果需要切換哪些數(shù)據(jù)源吨些,就需要把需要切換的數(shù)據(jù)源都配置出來)、數(shù)據(jù)源的切換配置(也就是上面我們定義的繼承AbstractRoutingDataSource的MultipleDataSourceToChoose的類)炒辉、SessionFactory的配置就和以前一樣豪墅,沒有任何變化(根據(jù)Hibernate和Mybatis的SessionFactory配置)。
<!-- 配置數(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>
<!-- 切換數(shù)據(jù)源-->
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è)置默認(rèn)的目標(biāo)數(shù)據(jù)源 -->
<property name="defaultTargetDataSource" ref="dataSource0" />
</bean>
<!--SessionFactory的配置黔寇,這里就以Hibernate為例-->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<!-- 注意這里的DataSource數(shù)據(jù)源就是切換的數(shù)據(jù)源偶器,因為它會給我們提供一個代理的數(shù)據(jù)源,至于是提供那個數(shù)據(jù)源跟我們代碼中的選擇-->
<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>
<!-- 事務(wù)的配置我們就省略啦缝裤,和之前的配置一樣屏轰,只是數(shù)據(jù)源依然是切換數(shù)據(jù)源的id-->
接下來我們就進行測試:測試我們先對AOP動態(tài)代理切換數(shù)據(jù)源的方式進行測試,然后對手動切換數(shù)據(jù)源進行測試
AOP動態(tài)代理切換數(shù)據(jù)源測試
@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);
}
//切換到dataSource0
@DynamicSwitchDataSource(dataSource = "datasource0")
public void save(User user) {
userDao.save(user);
}
//切換到dataSource1
@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;
}
}
手動方式切換數(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)原理
實現(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)閉和打開連接的開銷。這里提供一個淺談數(shù)據(jù)庫連接池說的簡單易懂 检眯。 Connection getConnection() throws SQLException;所以這句話總是要執(zhí)行的厘擂,只是AbstractRoutingDataSource這個類給我們進行了一些中介的處理,在獲取Connection的時候會去尋找保存的DataSource的引用锰瘸,到底是選擇哪個DataSource進行處理刽严,看代碼!
下面就給出繼承圖:
graph LR
MultipleDataSourceToChoose-->AbstractRoutingDataSource
AbstractRoutingDataSource-->InitializingBean
AbstractRoutingDataSource-->AbstractDataSource
AbstractDataSource-->DataSource
第一:我們?nèi)タ磘argetDataSource的源碼看究竟是什么
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
通過源碼我們發(fā)現(xiàn)targetDataSource就是一個Map對象避凝。
第二:我們?nèi)タ葱~@取連接getConnection的源代碼舞萄,看是怎么獲取的
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
通過源代碼眨补,我們發(fā)現(xiàn),是determineTargetDataSource()方法返回一個DataSource倒脓,然后就可以獲取連接啦渤涌!
第三:關(guān)鍵的一步,我們?nèi)タ聪耫etermineTargetDataSource()方法是如何獲取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");
//determineCurrentLookupKey()方法就是我們重寫的一個方法把还,他就是為targetDataSources的Map對象返回一個key
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;
}
通過這里我們應(yīng)該就很清楚整個流程啦!