Spring AOP切面解決數(shù)據(jù)庫(kù)讀寫(xiě)分離實(shí)例詳解
Spring AOP切面解決數(shù)據(jù)庫(kù)讀寫(xiě)分離實(shí)例詳解
為了減輕數(shù)據(jù)庫(kù)的壓力,一般會(huì)使用數(shù)據(jù)庫(kù)主從(master/slave)的方式,但是這種方式會(huì)給應(yīng)用程序帶來(lái)一定的麻煩,比如說(shuō),應(yīng)用程序如何做到把數(shù)據(jù)寫(xiě)到master庫(kù),而讀取數(shù)據(jù)的時(shí)候,從slave庫(kù)讀取。如果應(yīng)用程序判斷失誤,把數(shù)據(jù)寫(xiě)入到slave庫(kù),會(huì)給系統(tǒng)造成致命的打擊。
解決讀寫(xiě)分離的方案很多,常用的有SQL解析、動(dòng)態(tài)設(shè)置數(shù)據(jù)源。SQL解析主要是通過(guò)分析sql語(yǔ)句是insert/select/update/delete中的哪一種,從而對(duì)應(yīng)選擇主從。而動(dòng)態(tài)設(shè)置數(shù)據(jù)源,則是通過(guò)攔截方法名稱(chēng)的方式來(lái)決定主從的,例如:save*(),insert*() 形式的方法使用master庫(kù),select()開(kāi)頭的,使用slave庫(kù)。蠻多公司會(huì)使用在方法上標(biāo)上自定義的@Master、@Slave之類(lèi)的標(biāo)簽來(lái)選擇主從,也有公司直接就調(diào)用setxxMaster,setxxSlave之類(lèi)的代碼進(jìn)行主從選擇。
下面我主要介紹一下基于Spring AOP動(dòng)態(tài)設(shè)置數(shù)據(jù)源這種方式。注意這篇文章是基于自己項(xiàng)目的實(shí)際情況的,不是通用的方案,請(qǐng)知曉。
原理圖
Spring AOP的切面主要的職責(zé)是攔截Mybatis的Mapper接口,通過(guò)判斷Mapper接口中的方法名稱(chēng)來(lái)決定主從。
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接口類(lèi)都放置在persistence下。配置的切面類(lèi)是ReadWriteInterceptor。這樣當(dāng)Mapper接口的方法被調(diào)用時(shí),會(huì)先調(diào)用這個(gè)切面類(lèi)的readOrWriteDB方法。在這里需要注意<aop:aspect>中的order="1" 配置,主要是為了解決切面于切面之間的優(yōu)先級(jí)問(wèn)題,因?yàn)檎麄€(gè)系統(tǒng)中不太可能只有一個(gè)切面類(lèi)。
Spring AOP 切面類(lèi)實(shí)現(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類(lèi)中的getConnection方法
ReadWriteInterceptor中的readOrWriteDB方法只是決定選擇主還是從,我們還必須覆蓋數(shù)據(jù)源的getConnection方法,以便獲取正確的connection。一般來(lái)說(shuō),是一主多從,即一個(gè)master庫(kù),多個(gè)slave庫(kù)的,所以還得解決多個(gè)slave庫(kù)之間負(fù)載均衡、故障轉(zhuǎn)移以及失敗重連接等問(wèn)題。
1、負(fù)載均衡問(wèn)題,slave不多,系統(tǒng)并發(fā)讀不高的話(huà),直接使用隨機(jī)數(shù)訪(fǎng)問(wèn)也是可以的。就是根據(jù)slave的臺(tái)數(shù),然后產(chǎn)生隨機(jī)數(shù),隨機(jī)的訪(fǎng)問(wèn)slave。
2、故障轉(zhuǎn)移,如果發(fā)現(xiàn)connection獲取不到了,則把它從slave列表中移除,等其回復(fù)后,再加入到slave列表中
3、失敗重連,第一次連接失敗后,可以多嘗試幾次,如嘗試10次。
處理業(yè)務(wù)方法中的@Transactional注解
我參與的這個(gè)項(xiàng)目,大部分業(yè)務(wù)代碼是不需要事務(wù)的,只有極個(gè)別情況需要。那么按照上面提到的方案,如果不對(duì)業(yè)務(wù)方法中@Transactional注解進(jìn)行特殊處理的話(huà),主從的選擇會(huì)出現(xiàn)問(wèn)題。大家都知道,如果使用了Spring的事務(wù),那么在同一個(gè)業(yè)務(wù)方法內(nèi),只會(huì)調(diào)用一次數(shù)據(jù)源的getConnection方法,如果該業(yè)務(wù)方法內(nèi),調(diào)用的mapper接口剛好以select開(kāi)頭的,就會(huì)選擇slave庫(kù),那么接下來(lái)調(diào)用以insert開(kāi)頭的mapper接口方法時(shí),會(huì)把數(shù)據(jù)寫(xiě)入到slave庫(kù)。如何解決這個(gè)問(wèn)題呢?必須在進(jìn)入標(biāo)有@Transactional注解的業(yè)務(wù)方法前,指定選擇master主庫(kù)??梢酝ㄟ^(guò)覆蓋DataSourceTransactionManager類(lèi)中的doBegin方法,如下:
public class MyTransactionManager extendsDataSourceTransactionManager{ @Override protected void doBegin(Object transaction, TransactionDefinitiondefinition) { //選擇master數(shù)據(jù)庫(kù) super.doBegin(transaction, definition); } }
這樣既可以避免,把數(shù)據(jù)寫(xiě)入到從庫(kù)的問(wèn)題。
總結(jié)
本人的解決方案是基于項(xiàng)目實(shí)際的,不一定合適你,我只是展示了解決方案而已。當(dāng)然你可以選擇開(kāi)源的框架,像阿里的Cobar,360的Atlas。
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- Spring AOP使用接口方式實(shí)現(xiàn)
- Springboot接口項(xiàng)目如何使用AOP記錄日志
- Spring AOP實(shí)現(xiàn)Redis緩存數(shù)據(jù)庫(kù)查詢(xún)?cè)创a
- Spring+Mybatis 實(shí)現(xiàn)aop數(shù)據(jù)庫(kù)讀寫(xiě)分離與多數(shù)據(jù)庫(kù)源配置操作
- 使用Spring AOP實(shí)現(xiàn)MySQL數(shù)據(jù)庫(kù)讀寫(xiě)分離案例分析(附demo)
- Spring AOP實(shí)現(xiàn)接口請(qǐng)求記錄到數(shù)據(jù)庫(kù)的示例代碼
相關(guān)文章
JSP實(shí)現(xiàn)簡(jiǎn)單網(wǎng)頁(yè)計(jì)算器
這篇文章主要為大家詳細(xì)介紹了JSP實(shí)現(xiàn)簡(jiǎn)單網(wǎng)頁(yè)計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02用Java實(shí)現(xiàn)FTP服務(wù)器解決方案
用Java實(shí)現(xiàn)FTP服務(wù)器解決方案...2006-10-10json實(shí)現(xiàn)jsp分頁(yè)實(shí)例介紹(附效果圖)
json的既簡(jiǎn)單易懂,又傳輸迅速。并且能和javascript很好的融為一體;在不需要添加jar的前提下,能夠很好完成jsp分頁(yè)問(wèn)題,接下來(lái)為大家介紹下如何實(shí)現(xiàn)2013-04-04讀取數(shù)據(jù)庫(kù)的數(shù)據(jù)并整合成3D餅圖在jsp中顯示詳解
這篇文章主要給大家介紹了關(guān)于讀取數(shù)據(jù)庫(kù)的數(shù)據(jù)并整合成3D餅圖在jsp中顯示的相關(guān)資料,文中通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-07-07