Spring AOP切面解決數(shù)據(jù)庫讀寫分離實例詳解
Spring AOP切面解決數(shù)據(jù)庫讀寫分離實例詳解
為了減輕數(shù)據(jù)庫的壓力,一般會使用數(shù)據(jù)庫主從(master/slave)的方式,但是這種方式會給應用程序帶來一定的麻煩,比如說,應用程序如何做到把數(shù)據(jù)寫到master庫,而讀取數(shù)據(jù)的時候,從slave庫讀取。如果應用程序判斷失誤,把數(shù)據(jù)寫入到slave庫,會給系統(tǒng)造成致命的打擊。
解決讀寫分離的方案很多,常用的有SQL解析、動態(tài)設置數(shù)據(jù)源。SQL解析主要是通過分析sql語句是insert/select/update/delete中的哪一種,從而對應選擇主從。而動態(tài)設置數(shù)據(jù)源,則是通過攔截方法名稱的方式來決定主從的,例如:save*(),insert*() 形式的方法使用master庫,select()開頭的,使用slave庫。蠻多公司會使用在方法上標上自定義的@Master、@Slave之類的標簽來選擇主從,也有公司直接就調用setxxMaster,setxxSlave之類的代碼進行主從選擇。
下面我主要介紹一下基于Spring AOP動態(tài)設置數(shù)據(jù)源這種方式。注意這篇文章是基于自己項目的實際情況的,不是通用的方案,請知曉。
原理圖

Spring AOP的切面主要的職責是攔截Mybatis的Mapper接口,通過判斷Mapper接口中的方法名稱來決定主從。
Spring AOP 切面配置
<aop:config expose-proxy="true">
<aop:pointcut id="txPointcut" expression="execution(* com.test..persistence..*.*(..))" />
<aop:aspect ref="readWriteInterceptor" order="1">
<aop:around pointcut-ref="txPointcut" method="readOrWriteDB"/>
</aop:aspect>
</aop:config>
<bean id="readWriteInterceptor" class="com.test.ReadWriteInterceptor">
<property name="readMethodList">
<list>
<value>query*</value>
<value>use*</value>
<value>get*</value>
<value>count*</value>
<value>find*</value>
<value>list*</value>
<value>search*</value>
</list>
</property>
<property name="writeMethodList">
<list>
<value>save*</value>
<value>add*</value>
<value>create*</value>
<value>insert*</value>
<value>update*</value>
<value>merge*</value>
<value>del*</value>
<value>remove*</value>
<value>put*</value>
<value>write*</value>
</list>
</property>
</bean>
把所有Mybatis接口類都放置在persistence下。配置的切面類是ReadWriteInterceptor。這樣當Mapper接口的方法被調用時,會先調用這個切面類的readOrWriteDB方法。在這里需要注意<aop:aspect>中的order="1" 配置,主要是為了解決切面于切面之間的優(yōu)先級問題,因為整個系統(tǒng)中不太可能只有一個切面類。
Spring AOP 切面類實現(xiàn)
public class ReadWriteInterceptor {
private static final String DB_SERVICE = "dbService";
private List<String> readMethodList = new ArrayList<String>();
private List<String> writeMethodList = new ArrayList<String>();
public Object readOrWriteDB(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
if (isChooseReadDB(methodName)) {
//選擇slave數(shù)據(jù)源
} else if (isChooseWriteDB(methodName)) {
//選擇master數(shù)據(jù)源
} else {
//選擇master數(shù)據(jù)源
}
return pjp.proceed();
}
private boolean isChooseWriteDB(String methodName) {
for (String mappedName : this.writeMethodList) {
if (isMatch(methodName, mappedName)) {
return true;
}
}
return false;
}
private boolean isChooseReadDB(String methodName) {
for (String mappedName : this.readMethodList) {
if (isMatch(methodName, mappedName)) {
return true;
}
}
return false;
}
private boolean isMatch(String methodName, String mappedName) {
return PatternMatchUtils.simpleMatch(mappedName, methodName);
}
public List<String> getReadMethodList() {
return readMethodList;
}
public void setReadMethodList(List<String> readMethodList) {
this.readMethodList = readMethodList;
}
public List<String> getWriteMethodList() {
return writeMethodList;
}
public void setWriteMethodList(List<String> writeMethodList) {
this.writeMethodList = writeMethodList;
}
覆蓋DynamicDataSource類中的getConnection方法
ReadWriteInterceptor中的readOrWriteDB方法只是決定選擇主還是從,我們還必須覆蓋數(shù)據(jù)源的getConnection方法,以便獲取正確的connection。一般來說,是一主多從,即一個master庫,多個slave庫的,所以還得解決多個slave庫之間負載均衡、故障轉移以及失敗重連接等問題。
1、負載均衡問題,slave不多,系統(tǒng)并發(fā)讀不高的話,直接使用隨機數(shù)訪問也是可以的。就是根據(jù)slave的臺數(shù),然后產(chǎn)生隨機數(shù),隨機的訪問slave。
2、故障轉移,如果發(fā)現(xiàn)connection獲取不到了,則把它從slave列表中移除,等其回復后,再加入到slave列表中
3、失敗重連,第一次連接失敗后,可以多嘗試幾次,如嘗試10次。
處理業(yè)務方法中的@Transactional注解
我參與的這個項目,大部分業(yè)務代碼是不需要事務的,只有極個別情況需要。那么按照上面提到的方案,如果不對業(yè)務方法中@Transactional注解進行特殊處理的話,主從的選擇會出現(xiàn)問題。大家都知道,如果使用了Spring的事務,那么在同一個業(yè)務方法內,只會調用一次數(shù)據(jù)源的getConnection方法,如果該業(yè)務方法內,調用的mapper接口剛好以select開頭的,就會選擇slave庫,那么接下來調用以insert開頭的mapper接口方法時,會把數(shù)據(jù)寫入到slave庫。如何解決這個問題呢?必須在進入標有@Transactional注解的業(yè)務方法前,指定選擇master主庫。可以通過覆蓋DataSourceTransactionManager類中的doBegin方法,如下:
public class MyTransactionManager extendsDataSourceTransactionManager{
@Override
protected void doBegin(Object transaction, TransactionDefinitiondefinition) {
//選擇master數(shù)據(jù)庫
super.doBegin(transaction, definition);
}
}
這樣既可以避免,把數(shù)據(jù)寫入到從庫的問題。
總結
本人的解決方案是基于項目實際的,不一定合適你,我只是展示了解決方案而已。當然你可以選擇開源的框架,像阿里的Cobar,360的Atlas。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關文章
讀取數(shù)據(jù)庫的數(shù)據(jù)并整合成3D餅圖在jsp中顯示詳解
這篇文章主要給大家介紹了關于讀取數(shù)據(jù)庫的數(shù)據(jù)并整合成3D餅圖在jsp中顯示的相關資料,文中通過示例代碼給大家介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面跟著小編來一起學習學習吧。2017-07-07

