代碼實現讀寫數據庫分離
武器
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。