複習電商筆記-22-SpringAOP實現讀寫分離
阿新 • • 發佈:2018-11-17
*SpringAOP實現讀寫分離
工作原理
使用SpringAOP動態切換資料來源。在呼叫service方法之前,使用AOP進行判斷,是使用讀庫還是使用寫庫。根據要執行的方法名呼叫不同的資料庫,例如使用query、find、get等開頭的方法就訪問讀庫,其他的訪問寫庫。
AOP切面實現動態資料來源
在後臺系統jt-manage中來改造:
com.jt.manage.datasource.DynamicDataSource
package com.jt.manage.datasource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 定義動態資料來源,實現通過整合Spring提供的AbstractRoutingDataSource,只需要實現determineCurrentLookupKey方法即可 * * 由於DynamicDataSource是單例的,執行緒不安全的,所以採用ThreadLocal保證執行緒安全,由DynamicDataSourceHolder完成。 * */ public class DynamicDataSource extends AbstractRoutingDataSource{ @Override protected Object determineCurrentLookupKey() { // 使用DynamicDataSourceHolder保證執行緒安全,並且得到當前執行緒中的資料來源key return DynamicDataSourceHolder.getDataSourceKey(); } }
com.jt.manage.datasource.DynamicDataSourceHolder
package com.jt.manage.datasource; /** * * 使用ThreadLocal技術來記錄當前執行緒中的資料來源的key * */ public class DynamicDataSourceHolder { //寫庫對應的資料來源key private static final String MASTER = "master"; //讀庫對應的資料來源key private static final String SLAVE = "slave"; //使用ThreadLocal記錄當前執行緒的資料來源key private static final ThreadLocal<String> holder = new ThreadLocal<String>(); /** * 設定資料來源key * @param key */ public static void putDataSourceKey(String key) { holder.set(key); } /** * 獲取資料來源key * @return */ public static String getDataSourceKey() { return holder.get(); } /** * 標記寫庫 */ public static void markMaster(){ putDataSourceKey(MASTER); } /** * 標記讀庫 */ public static void markSlave(){ putDataSourceKey(SLAVE); } }
com.jt.manage.datasource.DataSourceAspect
package com.jt.manage.datasource; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; /** * 定義資料來源的AOP切面,通過該Service的方法名判斷是應該走讀庫還是寫庫 * */ public class DataSourceAspect { /** * 在進入Service方法之前執行 * * @param point 切面物件 */ public void before(JoinPoint point) { // 獲取到當前執行的方法名 String methodName = point.getSignature().getName(); if (isSlave(methodName)) { // 標記為讀庫 DynamicDataSourceHolder.markSlave(); } else { // 標記為寫庫 DynamicDataSourceHolder.markMaster(); } } /** * 判斷是否為讀庫 * * @param methodName * @return */ private Boolean isSlave(String methodName) { // 方法名以query、find、get開頭的方法名走從庫 return StringUtils.startsWithAny(methodName, "query", "find", "get"); } }
增加兩個資料來源 jdbc.properties
jdbc.master.driver=com.mysql.jdbc.Driver
jdbc.master.url=jdbc:mysql://127.0.0.1:3308/jtdb?useUnicode=true&cha
racterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.master.username=root
jdbc.master.password=root
jdbc.slave01.driver=com.mysql.jdbc.Driver
jdbc.slave01.url=jdbc:mysql://127.0.0.1:3309/jtdb?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.slave01.username=root
jdbc.slave01.password=root
引用兩個資料來源 applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 使用spring自帶的佔位符替換功能,可以實現註解方式獲取屬性檔案中的配置值 -->
<bean
class="com.jt.common.spring.exetend.ExtendedPropertyPlaceholderConfigurer">
<!-- 允許JVM引數覆蓋 -->
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<!-- 忽略沒有找到的資原始檔 -->
<property name="ignoreResourceNotFound" value="true" />
<!-- 配置資原始檔 -->
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
<value>classpath:env.properties</value>
<value>classpath:httpclient.properties</value>
<value>classpath:redis.properties</value>
<value>classpath:rabbitmq.properties</value>
</list>
</property>
</bean>
<!-- 掃描包 -->
<context:component-scan base-package="com.jt" />
<!-- 定義資料來源,使用自己實現的資料來源 -->
<bean id="dataSource" class="com.jt.manage.datasource.DynamicDataSource">
<!-- 設定多個數據源 -->
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- 這個key需要和程式中的key一致 -->
<entry key="master" value-ref="masterDataSource" />
<entry key="slave" value-ref="slave01DataSource" />
</map>
</property>
<!-- 設定預設的資料來源,這裡預設走寫庫 -->
<property name="defaultTargetDataSource" ref="masterDataSource" />
</bean>
<!-- 配置連線池 -->
<bean id="masterDataSource" class="com.jolbox.bonecp.BoneCPDataSource"
destroy-method="close">
<!-- 資料庫驅動 -->
<property name="driverClass" value="${jdbc.master.driver}" />
<!-- 相應驅動的jdbcUrl -->
<property name="jdbcUrl" value="${jdbc.master.url}" />
<!-- 資料庫的使用者名稱 -->
<property name="username" value="${jdbc.master.username}" />
<!-- 資料庫的密碼 -->
<property name="password" value="${jdbc.master.password}" />
<!-- 檢查資料庫連線池中空閒連線的間隔時間,單位是分,預設值:240,如果要取消則設定為0 -->
<property name="idleConnectionTestPeriod" value="60" />
<!-- 連線池中未使用的連結最大存活時間,單位是分,預設值:60,如果要永遠存活設定為0 -->
<property name="idleMaxAge" value="30" />
<!-- 每個分割槽最大的連線數 -->
<property name="maxConnectionsPerPartition" value="150" />
<!-- 每個分割槽最小的連線數 -->
<property name="minConnectionsPerPartition" value="5" />
</bean>
<!-- 配置連線池 -->
<bean id="slave01DataSource" class="com.jolbox.bonecp.BoneCPDataSource"
destroy-method="close">
<!-- 資料庫驅動 -->
<property name="driverClass" value="${jdbc.slave01.driver}" />
<!-- 相應驅動的jdbcUrl -->
<property name="jdbcUrl" value="${jdbc.slave01.url}" />
<!-- 資料庫的使用者名稱 -->
<property name="username" value="${jdbc.slave01.username}" />
<!-- 資料庫的密碼 -->
<property name="password" value="${jdbc.slave01.password}" />
<!-- 檢查資料庫連線池中空閒連線的間隔時間,單位是分,預設值:240,如果要取消則設定為0 -->
<property name="idleConnectionTestPeriod" value="60" />
<!-- 連線池中未使用的連結最大存活時間,單位是分,預設值:60,如果要永遠存活設定為0 -->
<property name="idleMaxAge" value="30" />
<!-- 每個分割槽最大的連線數 -->
<property name="maxConnectionsPerPartition" value="150" />
<!-- 每個分割槽最小的連線數 -->
<property name="minConnectionsPerPartition" value="5" />
</bean>
</beans>
定義AOP切面處理
將切面應用到自定義的切面處理器上 applicationContext-transaction.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">
<!-- 定義事務管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 定義事務策略 -->
<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" />
<!-- 主庫執行操作,事務傳播行為定義為預設行為 -->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<!--其他方法使用預設事務策略 -->
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<!-- 定義AOP切面處理器 -->
<bean class="com.jt.manage.datasource.DataSourceAspect" id="dataSourceAspect" />
<aop:config>
<!-- 定義切面,所有的service的所有方法 -->
<aop:pointcut id="txPointcut" expression="execution(* com.jt.manage.service.*.*(..))" />
<!-- 應用事務策略到Service切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
<!-- 將切面應用到自定義的切面處理器上,-9999保證該切面優先順序最高執行 -->
<aop:aspect ref="dataSourceAspect" order="-9999">
<aop:before method="before" pointcut-ref="txPointcut" />
</aop:aspect>
</aop:config>
</beans>