思路參照 spring讀寫分離 - 事務配置篇(轉) ,不過是基於@Transactional判斷,所以每個需要事務的方法上都必須添加上這個註解,這裏直接貼出代碼:
配置文件:
多數據源配置:
<bean id="dataSource"
class="com.lmiky.platform.database.datasource.DynamicDataSource">
<property name="readDataSources">
<list>
<ref bean="readDataSource1" />
<ref bean="readDataSource2" />
</list>
</property>
<property name="writeDataSource" ref="writeDataSource" />
</bean>
數據源攔截器:
<bean id="dateSourceAspect"
class="com.lmiky.platform.database.datasource.DateSourceAspect" />
<aop:config expose-proxy="true">
<aop:aspect ref="dateSourceAspect" order="0">
<aop:pointcut id="dateSourcePointcut"
expression="execution(* com.lmiky..service.impl..*.*(..))"/>
<aop:around pointcut-ref="dateSourcePointcut" method="determineReadOrWriteDB" />
</aop:aspect>
</aop:config>
要保證讓這個攔截在事務的攔截器之前,否則如果spring先攔截事務的話,就不會起效了。可以用order設的值來排序,或者把這個配置的代碼放在跟事務配置的代碼同一個頁面,並且放在事務配置代碼的前面,spring是按代碼順序來執行的。
其他的跟單個數據源配置一樣。
java代碼:
package com.lmiky.platform.database.datasource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
/**
* 數據源切片
*
* @author lmiky
* @date 2015年9月7日 下午3:25:54
*/
public class DateSourceAspect {
/**
* 緩存
*/
private static ConcurrentHashMap<String, Boolean> methodIsReadCache = new ConcurrentHashMap<>();
/**
* 決策是否只讀
*
* @param pjp 織入點
* @return 方法執行結果
* @throws Throwable
* @author lmiky
* @date 2015年9月7日 下午3:45:27
*/
public Object determineReadOrWriteDB(ProceedingJoinPoint pjp) throws Throwable {
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
Object target = pjp.getTarget();
String cacheKey = target.getClass().getName() + "." + method.getName();
Boolean isReadCacheValue = methodIsReadCache.get(cacheKey);
if (isReadCacheValue == null) {
// 重新獲取方法,否則傳遞的是接口的方法信息
Method realMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
isReadCacheValue = isChoiceReadDB(realMethod);
methodIsReadCache.put(cacheKey, isReadCacheValue);
}
if (isReadCacheValue) {
DynamicDataSourceHolder.markRead();
} else {
DynamicDataSourceHolder.markWrite();
}
try {
return pjp.proceed();
} finally {
DynamicDataSourceHolder.reset();
}
}
/**
* 判斷是否只讀方法
*
* @param method 執行方法
* @return 當前方法是否只讀
* @author lmiky
* @date 2015年9月7日 下午3:45:10
*/
private boolean isChoiceReadDB(Method method) {
Transactional transactionalAnno = AnnotationUtils.findAnnotation(method, Transactional.class);
if (transactionalAnno == null) {
return true;
}
// 如果之前選擇了寫庫,則現在還選擇寫庫
if (DynamicDataSourceHolder.isChoiceWrite()) {
return false;
}
if (transactionalAnno.readOnly()) {
return true;
}
return false;
}
}
數據源選擇:
package com.lmiky.platform.database.datasource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 動態數據源
*
* @author lmiky
* @date 2015年9月7日 下午2:01:31
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private Object writeDataSource;
private List<Object> readDataSources;
private int readDataSourceSize = 0;
private AtomicInteger readIndex = new AtomicInteger(0);
/**
* 數據源鍵名
*/
private static final String DATASOURCE_KEY_WRITE = "write";
private static final String DATASOURCE_KEY_READ = "read";
/* (non-Javadoc)
* @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
if (this.writeDataSource == null) {
throw new IllegalArgumentException("Property 'writeDataSource' is required");
}
setDefaultTargetDataSource(writeDataSource);
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DATASOURCE_KEY_WRITE, writeDataSource);
if (this.readDataSources == null) {
readDataSourceSize = 0;
} else {
for(int i=0; i<readDataSources.size(); i++) {
targetDataSources.put(DATASOURCE_KEY_READ + i, readDataSources.get(i));
}
readDataSourceSize = readDataSources.size();
}
setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
/*
* (non-Javadoc)
* @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()
*/
@Override
protected Object determineCurrentLookupKey() {
if(DynamicDataSourceHolder.isChoiceNone() || DynamicDataSourceHolder.isChoiceWrite() || readDataSourceSize == 0) {
return DATASOURCE_KEY_WRITE;
}
int index = readIndex.incrementAndGet() % readDataSourceSize;
return DATASOURCE_KEY_READ + index;
}
/**
* @return the writeDataSource
*/
public Object getWriteDataSource() {
return writeDataSource;
}
/**
* @param writeDataSource the writeDataSource to set
*/
public void setWriteDataSource(Object writeDataSource) {
this.writeDataSource = writeDataSource;
}
/**
* @return the readDataSources
*/
public List<Object> getReadDataSources() {
return readDataSources;
}
/**
* @param readDataSources the readDataSources to set
*/
public void setReadDataSources(List<Object> readDataSources) {
this.readDataSources = readDataSources;
}
}
package com.lmiky.platform.database.datasource;
/**
* 數據源管理器
*
* @author lmiky
* @date 2015年9月7日 下午2:02:23
*/
public class DynamicDataSourceHolder {
private static enum DataSourceType {
write, read;
}
public static final ThreadLocal<DataSourceType> holder = new ThreadLocal<>();
/**
* 數據源名稱
*/
public static final String DATASOURCE_WRITE = "write";
public static final String DATASOURCE_READ = "read";
/**
* 標記爲寫數據源
* @author lmiky
* @date 2015年9月9日 下午8:57:43
*/
public static void markWrite() {
holder.set(DataSourceType.write);
}
/**
* 標記爲讀數據源
* @author lmiky
* @date 2015年9月9日 下午8:57:43
*/
public static void markRead() {
holder.set(DataSourceType.read);
}
/**
* 重置
* @author lmiky
* @date 2015年9月9日 下午8:58:01
*/
public static void reset() {
holder.set(null);
}
/**
* 是否還未設置數據源
* @author lmiky
* @date 2015年9月9日 下午8:58:09
* @return
*/
public static boolean isChoiceNone() {
return null == holder.get();
}
/**
* 當前是否選擇了寫數據源
* @author lmiky
* @date 2015年9月9日 下午8:58:19
* @return
*/
public static boolean isChoiceWrite() {
return DataSourceType.write == holder.get();
}
/**
* 當前是否選擇了讀數據源
* @author lmiky
* @date 2015年9月9日 下午8:58:29
* @return
*/
public static boolean isChoiceRead() {
return DataSourceType.read == holder.get();
}
}