Spring多數據源配置系列(三)——讀寫分離
- 2019 年 10 月 30 日
- 筆記
版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/luo4105/article/details/77773027
資源
AbstractRoutingDataSource亦可用來做讀寫分離。讀寫分離實際上也算多數據源,有讀數據源、寫數據源,通過一定規則使寫用寫數據源,讀用讀數據源。
讀寫分離介紹
讀寫分離常用的策略有兩種,一種是使用MySql中間件,如mysql-proxy之類。這種對程式碼沒有侵入、沒有影響,運維就能完成、維護;第二種是在應用層解決,這裡介紹的就是應用層使用spring的AbstractRoutingDataSource來完成項目的讀寫分離。
現在比較常使用的是使用MySql中間件,這樣職責分開,也便於維護。但在一些創業公司人手不足,分工不能明確,這樣就可以選擇第二種,在應用層解決。
原理
我們通過service方法名頭來判斷是讀還是寫。比如」get,select」就是讀操作,」update,delete,save,insert」就是寫操作。
將讀寫操作關鍵字和方法名頭的對應關係保存起來,如:{「read」,[「get」,」 select」]}。這是一個配置項,可以用static Map<String,String[]>存放。
還需獲得方法名,這個可以用切面獲得,存放到ThreadLocal。
實現
實現步驟為
1. 配置讀寫數據源
2. 實現AbstractRoutingDataSource類
3. 註冊實現類
4. 編寫AOP
配置數據源
<bean id="readDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/aicms" /> <property name="username" value="root" /> <property name="password" value="123456" /> </bean> <bean id="writeDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/aicms" /> <property name="username" value="root" /> <property name="password" value="123456" /> </bean>
實現AbstractRoutingDataSource類
public class ReadWriteDynamicDataSource extends AbstractRoutingDataSource { public static Map<String, String[]> readWriteKey; public static ThreadLocal<String> currentServiceMethod = new ThreadLocal<>(); public void setReadWriteKey(Map<String, String> map) { ReadWriteDynamicDataSource.readWriteKey = new HashMap<>(); for(Map.Entry<String, String> item : map.entrySet()) { ReadWriteDynamicDataSource.readWriteKey.put(item.getKey(), item.getValue().split(",")); } } @Override protected Object determineCurrentLookupKey() { String serviceMethod = currentServiceMethod.get(); l:for(Map.Entry<String, String[]> item : readWriteKey.entrySet()){ for (String val : item.getValue()) { if(StringUtils.startsWithIgnoreCase(serviceMethod, val)) { System.out.println("datasource:" + item.getKey()); return item.getKey(); } } } return null; } }
註冊實現類
<bean id="dynamicDataSource" class="com.lc.rwrout.ReadWriteDynamicDataSource"> <property name="targetDataSources"> <map> <entry key="read" value-ref="readDataSource"/> <entry key="write" value-ref="writeDataSource"/> </map> </property> <property name="readWriteKey"> <map> <entry key="read" value="get,select" /> <entry key="write" value="update,del,save,add,insert" /> </map> </property> <property name="defaultTargetDataSource" ref="writeDataSource"> </property> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dynamicDataSource" /> <property name="mapperLocations" value="mapper/*.xml" /> <property name="typeAliasesPackage" value="com.lc.model" /> </bean>
編寫AOP
public class ReadWriteAspect { public Object around(ProceedingJoinPoint jp) throws Throwable { ReadWriteDynamicDataSource.currentServiceMethod.set(jp.getSignature().getName()); Object rvt = null; //執行原方法,也可以不執行 rvt = jp.proceed(jp.getArgs()); ReadWriteDynamicDataSource.currentServiceMethod.remove(); return rvt; } }
<bean id="dataSourceAspect" class="com.lc.rwrout.ReadWriteAspect"></bean> <aop:config> <aop:aspect id="aspect" ref="dataSourceAspect" order="0"> <aop:pointcut id="servicePointCut" expression="execution(* com.lc.service.*Service.*(..))" /> <aop:around method="around" pointcut-ref="servicePointCut" /> </aop:aspect> </aop:config>