代碼實現讀寫數據庫分離
武器
spring3.0以上版本
1、繼承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,自定義數據源路由。
2、實現數據源類型管理工具,諸如DBContextHolder,包含設置和讀取當前數據源配置。
3、實現數據源切換的AOP。
4、自定義只讀注解,諸如@ReadOnlyKey。
5、配置transactionManager,實現aop。
1、自定義的DynamicDataSource
public class DynamicDataSource extends AbstractRoutingDataSource { /** * 自動查找數據源 * * @return 數據源名 */ @Override protected Object determineCurrentLookupKey() { String dataSource = getDataSource(); return dataSource; } }
2、數據源類型管理工具DBContextHolder
public abstract class DBContextHolder { /** * 數據源類型管理 * <p> * 考慮多線程,為保證線程之間互不干擾,所以使用ThreadLocal作線程隔離;<br> * 參數是數據源鍵值 * </p> * * @see ThreadLocal */ private static ThreadLocal<String> contextHolder = new ThreadLocal<String>(); /** * 數據庫源類型 * <p> * 配置數據源的時候,請遵守以下約束:<br> * 讀寫:dataSourceKeyRW;<br> * 讀:dataSourceKeyR. * </p> */ public enum DbType { DB_TYPE_RW("dataSourceKeyRW"), DB_TYPE_R("dataSourceKeyR"); private String dataSourceKey; DbType(String dataSourceKey) { this.dataSourceKey = dataSourceKey; } public String getDataSourceKey() { return dataSourceKey; } } /** * 獲取數據源 * <p> * 如果未設置,默認返回讀數據源 * </p> * * @return 數據源鍵值 */ public static String getDataSource() { String dataSource = contextHolder.get(); if (StringUtils.isEmpty(dataSource)) { dataSource = DbType.DB_TYPE_RW.dataSourceKey; } return dataSource; } /** * 設置數據源 * * @param dataSourceKey 數據源鍵值 */ public static void setDataSource(String dataSourceKey) { contextHolder.set(dataSourceKey); } }
注:定義了DbType枚舉,分別定義了讀和寫的數據源鍵值。
3、實現AOP。
public class DataSourceSwitchingAop { /** * 設置切點數據源 * <p> * 調試輸出數據源. * </p> * * @param joinPoint 切點 * @param dataSourceKey 當前數據源鍵值 */ private void setDataSourceByKey(JoinPoint joinPoint, String dataSourceKey) { setDataSource(dataSourceKey); debugLog(joinPoint.getTarget().getClass().getSimpleName() + "." + joinPoint.getSignature().getName() + "配置數據源:" + getDataSource()); } /** * 切換數據源 * <p> * 切換優先級由高到底如下;方法上注解DataSourceKey,方法上注解ReadOnlyKey,類上注解DataSourceKey;<br> * 如果未注解,則默認設置寫數據源. * </p> * * @param joinPoint 切點 * @see DataSourceKey * @see ReadOnlyKey * @see DbType */ public void switchDataSource(JoinPoint joinPoint) { Class<?> targetClass = joinPoint.getTarget().getClass(); String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); DataSourceKey dataSourceKey = getAnnotationClassMethod(targetClass, methodName, DataSourceKey.class, args); if (dataSourceKey != null) { setDataSourceByKey(joinPoint, dataSourceKey.dataSourceKey()); return; } ReadOnlyKey readOnlyKey = getAnnotationClassMethod(targetClass, methodName, ReadOnlyKey.class, args); if (readOnlyKey != null) { setDataSourceByKey(joinPoint, DbType.DB_TYPE_R.getDataSourceKey()); return; } dataSourceKey = (DataSourceKey) targetClass.getAnnotation(DataSourceKey.class); if (dataSourceKey != null) { setDataSourceByKey(joinPoint, dataSourceKey.dataSourceKey()); return; } setDataSourceByKey(joinPoint, DbType.DB_TYPE_RW.getDataSourceKey()); } }
4、自定義只讀注解,@ReadOnlyKey
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ReadOnlyKey { }
5、配置transaction和AOP
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dynamicDataSource"/> </bean>
<bean id="dataSourceSwitchingAop" class="com.xxx.common.framework2x.dao.DataSourceSwitchingAop"/>
<aop:config> <aop:aspect id="dataSourceSwitching" ref="dataSourceSwitchingAop" order="0"> <aop:pointcut id="dataSourceSwitchingService" expression="execution(* com.xxx.manager..*.*(..))"/> <aop:before method="switchDataSource" pointcut-ref="dataSourceSwitchingService"/> </aop:aspect> </aop:config>
以上就完成了基於注解實現動態切換讀寫數據源。
6、如果想要實現多數據源的切換,則可以自定義注解@DataSourceKey
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface DataSourceKey { /** * 配置數據源鍵值 * <p> * 默認:dataSource. * </p> * * @return 鍵值 */ String dataSourceKey() default "dataSource"; }
在接口方法上增加注解即可。
1、切換數據源的事務需要放到數據庫事務開啟前執行。針對上述代碼示例中,配置aop時需要指定order(值越小,執行越靠前)
<aop:config> <aop:aspect id="dataSourceSwitching" ref="dataSourceSwitchingAop" order="0"> <aop:pointcut id="dataSourceSwitchingService" expression="execution(* com.xxx.manager..*.*(..))"/> <aop:before method="switchDataSource" pointcut-ref="dataSourceSwitchingService"/> </aop:aspect> </aop:config>
2、@DataSourceKey可以加在method上,也可以加到class上,優先級是method>class。
3、@ReadOnlyKey只能加到method上。
4、@DatasourceKey和@ReadOnlyKey可以在一個class中混用,優先級是method的@DatasourceKey>method的@ReadOnlyKey>class的@DatasourceKey。