基於spring的aop實現多數據源動態切換
https://lanjingling.github.io/2016/02/15/spring-aop-dynamicdatasource/
基於spring的aop實現多數據源動態切換
發表於 2016-02-15 | 分類於 spring |一、多數據源動態切換原理
項目中我們經常會遇到多數據源的問題,尤其是數據同步或定時任務等項目更是如此;又例如:讀寫分離數據庫配置的系統。
1、多數據源設置:
1)靜態數據源切換:
一般情況下,我們可以配置多個數據源,然後為每個數據源寫一套對應的sessionFactory和dao層代碼(以hibernate為例,mybatis同理),——我們稱之為靜態數據源配置
2)動態數據源切換:
可看出在Dao層代碼中寫死了兩個SessionFactory,這樣日後如果再多一個數據源,還要改代碼添加一個SessionFactory,顯然這並不符合開閉原則。比較好的做法是,配置多個數據源,只對應一套sessionFactory,數據源之間可以動態切換。
2、動態數據源切換時,如何保證數據庫的事務:
目前事務最靈活的方式,是使用spring的聲明式事務,本質是利用了spring的aop,在執行數據庫操作前後,加上事務處理。
spring的事務管理,是基於數據源的,所以如果要實現動態數據源切換,而且在同一個數據源中保證事務是起作用的話,就需要註意二者的順序問題,即:在事物起作用之前就要把數據源切換回來。
舉一個例子:web開發常見是三層結構:controller、service、dao。一般事務都會在service層添加,如果使用spring的聲明式事物管理,在調用service層代碼之前,spring會通過aop的方式動態添加事務控制代碼,所以如果要想保證事物是有效的,那麽就必須在spring添加事務之前把數據源動態切換過來,也就是動態切換數據源的aop要至少在service上添加,而且要在spring聲明式事物aop之前添加.根據上面分析:
- 最簡單的方式是把動態切換數據源的aop加到controller層,這樣在controller層裏面就可以確定下來數據源了。不過,這樣有一個缺點就是,每一個controller綁定了一個數據源,不靈活。對於這種:一個請求,需要使用兩個以上數據源中的數據完成的業務時,就無法實現了。
- 針對上面的這種問題,可以考慮把動態切換數據源的aop放到service層,但要註意一定要在事務aop之前來完成。這樣,對於一個需要多個數據源數據的請求,我們只需要在controller裏面註入多個service實現即可。但這種做法的問題在於,controller層裏面會涉及到一些不必要的業務代碼,例如:合並兩個數據源中的list…
- 此外,針對上面的問題,還可以再考慮一種方案,就是把事務控制到dao層,然後在service層裏面動態切換數據源。
二、實例1:
本例子中,對不同數據源分包(package)管理,同一包下的代碼使用了同一數據源。
1、寫一個DynamicDataSource類繼承AbstractRoutingDataSource,並實現determineCurrentLookupKey方法:
1
|
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
2、利用ThreadLocal解決線程安全問題:
1
|
public class CustomerContextHolder {
|
3、定義一個數據源切面類,通過aop來控制數據源的切換:
1
|
import org.aspectj.lang.JoinPoint;
|
4、在spring的application.xml中配置多個dataSource:
1
|
<!-- 數據源1 -->
|
三、實例2:
該例子,實現了在業務邏輯層控制了mysql的讀寫分離。同樣,使用了spring的aop來動態切換讀和寫的數據源。和上個例子不同之處在於:不同數據源沒有按照包分類管理,而是使用了自定義註解。
1、首先配置mysql 的主從復制:
詳情見,這裏。
2、自定義註解:
1
|
import java.lang.annotation.ElementType;
|
3、基於spring的aop實現多數據源(讀和寫兩個數據源):
1)寫一個ChooseDataSource: 類繼承AbstractRoutingDataSource,並實現determineCurrentLookupKey方法:
1
|
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
|
2)利用ThreadLocal解決線程安全問題:
1
|
public class HandleDataSource {
|
3)定義一個數據源切面類,通過aop訪問,獲取方法上的自定義註解,然後根據註解內容盡情判斷,動態設置數據源:
1
|
import java.lang.reflect.Method;
|
4)配置applicationContext.xml:
1
|
<!-- 主庫數據源 -->
|
4、測試:
1
|
@DataSource("write")
|
- 測試寫操作:可以通過應用修改數據,修改主庫數據,發現從庫的數據被同步更新了,所以定義的write操作都是走的寫庫
- 測試讀操作: 後臺修改從庫數據,查看主庫的數據沒有被修改,在應用頁面中刷新,發現讀的是從庫的數據,說明讀寫分離ok。
基於spring的aop實現多數據源動態切換