? ? ? ? 大部分應(yīng)用都是讀多寫少,也就說對(duì)數(shù)據(jù)庫(kù)讀取數(shù)據(jù)的壓力比較大。我們可以采用讀寫分離的模式減少數(shù)據(jù)庫(kù)讀的壓力。基于應(yīng)用層的讀寫分離方案,多數(shù)據(jù)源切換方便,由程序自動(dòng)完成。下面是基于應(yīng)用的讀寫分離方案的模式:

解決方案
一、原理

? ? ? ? 在進(jìn)入service之前,使用AOP來作出判斷,是使用寫庫(kù)還是使用讀庫(kù),判斷依據(jù)可以根據(jù)方法名進(jìn)行判斷,比如說以query、find、get等開頭的就走讀庫(kù),其他的走寫庫(kù)。
二、具體代碼實(shí)現(xiàn)
1、DynamicDataSource
由于DynamicDataSource是單例的,線程不安全的,所以采用ThreadLocal來保證線程安全,由DynamicDataSourceHolder完成。
public class?DynamicDataSource?extends AbstractRoutingDataSource{
? ??????@Override
? ??????protected Object determineCurrentLookupKey() {?
? ? ? ? ? ? ? ? //使用DynamicDataSourceHolder保證線程安全,并且得到當(dāng)前線程中的數(shù)據(jù)源key
? ??????????????return DynamicDataSourceHolder.getDataSourceKey();
? ??????}?
}
2、DynamicDataSourceHolder
public class?DynamicDataSourceHolder{
? ??????? //寫庫(kù)對(duì)應(yīng)的數(shù)據(jù)源key
? ? ? ?private static final String MASTER = "master";
? ? ? ? ?//讀庫(kù)對(duì)應(yīng)的數(shù)據(jù)源key?
? ??????private static final String SLAVE = "slave"; ?
? ??????//使用ThreadLocal記錄當(dāng)前線程的數(shù)據(jù)源key
? ??????private static final ThreadLocal<String> holder = new ThreadLocal<String>();?
? ? ? ? ?//設(shè)置數(shù)據(jù)源
? ??????public static void putDataSourceKey(String key) {? ? ? ? ?
? ??????????????????holder.set(key);? ? ??
? ??????}?
? ? ? ? //獲取數(shù)據(jù)源
? ? ? ? public static String getDataSourceKey() {? ? ? ? ? ? ??
? ??????????????????return holder.get();?
? ? ? ? ?}
? ? ? ? //標(biāo)記寫數(shù)據(jù)庫(kù)
? ??????public static void markMaster(){? ? ? ??
? ??????????????????putDataSourceKey(MASTER);? ? ?
? ??????}??
? ? ? ? //標(biāo)記讀數(shù)據(jù)庫(kù)
? ? ? ??public static void markSlave(){? ? ? ? ? ? ??
? ??????????????????putDataSourceKey(SLAVE);
? ? ? ? }
}
3、DataSourceAspect
定義數(shù)據(jù)源的AOP切面,通過該Service的方法名判斷是應(yīng)該走讀庫(kù)還是寫庫(kù)?
public class DataSourceAspect{
? ? ? ? //在進(jìn)入Service方法之前執(zhí)行
? ??????public void before(JoinPoint point) {
? ??????????????// 獲取到當(dāng)前執(zhí)行的方法名?
? ? ? ? ? ? ? ? ?String methodName = point.getSignature().getName();?
? ? ? ? ? ? ? ? ?if (isSlave(methodName)) {
? ??????????????????????????// 標(biāo)記為讀庫(kù)
? ??????????????????????????DynamicDataSourceHolder.markSlave();
? ? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 標(biāo)記為寫庫(kù)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?DynamicDataSourceHolder.markMaster();
? ? ? ? ? ? ? ? ? }
????????}
? ? ? ? //判斷是否走讀庫(kù)
? ??????private Boolean isSlave(String methodName) {? ? ? ? ??
? ???????????????// 方法名以query、find、get開頭的方法名走從庫(kù)? ? ? ? ? ? ? }?
? ? ? ? ? ? ? ? ?return StringUtils.startsWithAny(methodName, "query", "find", "get");
? ? ? ? }
}
4、配置2個(gè)數(shù)據(jù)源
jdbc.master.driver=com.mysql.jdbc.Driver
jdbc.master.url=jdbc:mysql://127.0.0.1:3306/mybatis_1128useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.master.username=root
jdbc.master.password=123456??
jdbc.slave01.driver=com.mysql.jdbc.Driver
jdbc.slave01.url=jdbc:mysql://127.0.0.1:3307/mybatis_1128useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.slave01.username=root
jdbc.slave01.password=123456
5、定義連接池
<!-- 配置連接池 -->???
<bean id="masterDataSource" class="com.jolbox.bonecp.BoneCPDataSource"?????? destroy-method="close">??????
????????<!-- 數(shù)據(jù)庫(kù)驅(qū)動(dòng) -->??????
????????<property name="driverClass"value="${jdbc.master.driver}" />??????
????????<!-- 相應(yīng)驅(qū)動(dòng)的jdbcUrl -->??????
????????<property name="jdbcUrl"value="${jdbc.master.url}" />??????
????????<!-- 數(shù)據(jù)庫(kù)的用戶名 -->??????
????????<property name="username"value="${jdbc.master.username}" />??????
????????<!-- 數(shù)據(jù)庫(kù)的密碼 -->??????
? ? ? ? <property name="password"value="${jdbc.master.password}" />??????
????????<!-- 檢查數(shù)據(jù)庫(kù)連接池中空閑連接的間隔時(shí)間,單位是分,默認(rèn)值:240,如果要取消則設(shè)置為0 -->? ? ? ? ?
????????property name="idleConnectionTestPeriod"value="60" />??????
????????<!-- 連接池中未使用的鏈接最大存活時(shí)間,單位是分,默認(rèn)值:60,如果要永遠(yuǎn)存活設(shè)置為0 -->??????
????????<property name="idleMaxAge"value="30" />??????
????????<!-- 每個(gè)分區(qū)最大的連接數(shù) -->??????
????????<property name="maxConnectionsPerPartition"value="150" />??????
????????<!-- 每個(gè)分區(qū)最小的連接數(shù) -->??????
????????<property name="minConnectionsPerPartition"value="5" />???
</bean>
<!-- 配置連接池 -->
<bean id="slave01DataSource" class="com.jolbox.bonecp.BoneCPDataSource" ?????? destroy-method="close">
? ??????<!-- 數(shù)據(jù)庫(kù)驅(qū)動(dòng) -->
? ??????<property name="driverClass" value="${jdbc.slave01.driver}" />
? ??????<!-- 相應(yīng)驅(qū)動(dòng)的jdbcUrl -->?
? ??????<property name="jdbcUrl" value="${jdbc.slave01.url}" />
? ??????<!-- 數(shù)據(jù)庫(kù)的用戶名 -->?
? ??????<property name="username" value="${jdbc.slave01.username}" />
? ??????<!-- 數(shù)據(jù)庫(kù)的密碼 -->
? ??????<property name="password" value="${jdbc.slave01.password}" />
? ??????<!-- 檢查數(shù)據(jù)庫(kù)連接池中空閑連接的間隔時(shí)間,單位是分,默認(rèn)值:240,如果要取消則設(shè)置為0 -->
? ? ? ? ?<property name="idleConnectionTestPeriod" value="60" />
? ??????<!-- 連接池中未使用的鏈接最大存活時(shí)間,單位是分,默認(rèn)值:60,如果要永遠(yuǎn)存活設(shè)置為0 -->
? ??????<property name="idleMaxAge" value="30" />
? ??????<!-- 每個(gè)分區(qū)最大的連接數(shù) -->
? ??????<property name="maxConnectionsPerPartition" value="150" />
? ? ? ? ?<!-- 每個(gè)分區(qū)最小的連接數(shù) -->
? ? ? ? ?<property name="minConnectionsPerPartition" value="5" />?
</bean>
6、定義DataSource
<!-- 定義數(shù)據(jù)源,使用自己實(shí)現(xiàn)的數(shù)據(jù)源 -->???
<bean id="dataSource" class="cn.itcast.usermanage.spring.DynamicDataSource">??????
????????<!-- 設(shè)置多個(gè)數(shù)據(jù)源 -->??????
? ? ? ?<property name="targetDataSources">??????????
? ? ? ?????<map key-type="java.lang.String">?????????????
????????????????<!-- 這個(gè)key需要和程序中的key一致 -->?????????????
????????????????<entry key="master"value-ref="masterDataSource"/>?????????????
????????????????<entry key="slave"value-ref="slave01DataSource"/>??????????
????????????</map>??????
????????</property>??????
????????<!-- 設(shè)置默認(rèn)的數(shù)據(jù)源,這里默認(rèn)走寫庫(kù) -->??????
????????<property name="defaultTargetDataSource"ref="masterDataSource"/>???
</bean>
7、定義事務(wù)管理器
<bean id="transactionManager"?????? class="org.springframework.jdbc.datasource.DataSourceTransactionManager">? ? ? ? ?
? ??????<property name="dataSource" ref="dataSource" />? ? ?
</bean>? ?
8、定義事務(wù)策略
<!-- 定義事務(wù)策略 -->???
<tx:advice id="txAdvice" transaction-manager="transactionManager">??????
????????<tx:attributes>??????????
????????????<!--定義查詢方法都是只讀的 -->??????????
????????????<tx:method name="query*"read-only="true" />??????????
????????????<tx:method name="find*" read-only="true" />??????????
????????????<tx:method name="get*"read-only="true" />???????????
????????????<!-- 主庫(kù)執(zhí)行操作,事務(wù)傳播行為定義為默認(rèn)行為 -->??????????
????????????<tx:method name="save*"propagation="REQUIRED" />??????????
????????????<tx:method name="update*"propagation="REQUIRED" />??????????
????????????<tx:method name="delete*"propagation="REQUIRED" />???????????
????????????<!--其他方法使用默認(rèn)事務(wù)策略 -->??????????
????????????<tx:method name="*"/>??????
????????</tx:attributes>???
</tx:advice>
9、定義切面
<!-- 定義AOP切面處理器 -->???
<bean class="cn.itcast.usermanage.spring.DataSourceAspect"id="dataSourceAspect" />????
<aop:config>??????
????????<!-- 定義切面,所有的service的所有方法 -->??????
????????<aop:pointcut? id="txPointcut"? expression="execution(* xx.xxx.xxxxxxx.service.*.*(..))"/>??????
????????<!-- 應(yīng)用事務(wù)策略到Service切面 -->??????
????????<aop:advisor advice-ref="txAdvice"? pointcut-ref="txPointcut"/>? ? ? ? ? ? ? ? ?
????????<!-- 將切面應(yīng)用到自定義的切面處理器上,-9999保證該切面優(yōu)先級(jí)最高執(zhí)行 -->??????
????????<aop:aspect ref="dataSourceAspect"order="-9999">??????????
????????????????<aop:before method="before"pointcut-ref="txPointcut" />??????
????????</aop:aspect>???
</aop:config>
? ? ? ? 至此,簡(jiǎn)單的基于應(yīng)用層的讀寫分離就實(shí)現(xiàn)了。