<property name="writeDataSource" ref="dataSource"/>
<property name="readDataSourceMap">
<map>
<entry key="readDataSource1" value-ref="dataSource"/>
<entry key="readDataSource1" value-ref="dataSourceR"/>
</map>
</property>
</bean>
<bean id="readWriteDataSourceTransactionProcessor" class="com.xx.datasource.ReadWriteDataSourceProcessor">
<property name="forceChoiceReadWhenWrite" value="true"/>
</bean>
<aop:config expose-proxy="true">
<!-- 只对业务逻辑层实施事务 -->
<aop:pointcut id="txPointcut" expression="execution(* com.xx.service..*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
<aop:aspect order="3" ref="readWriteDataSourceTransactionProcessor">
<aop:around pointcut-ref="txPointcut" method="determineReadOrWriteDB"/>
</aop:aspect>
</aop:config>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="readWriteDataSource" />
</bean>
<aop:config>
<aop:advisor pointcut="execution(* com.xx.service..*.*(..))" advice-ref="txAdvice" order="2"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>
<tx:annotation-driven transaction-manager="transactionManager" order="1"/>
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.util.CollectionUtils;
public class ReadWriteDataSource extends AbstractDataSource implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(ReadWriteDataSource.class);
private DataSource writeDataSource;
private Map<String, DataSource> readDataSourceMap;
private String[] readDataSourceNames;
private DataSource[] readDataSources;
private int readDataSourceCount;
private AtomicInteger counter = new AtomicInteger(1);
/**
* 设置读库(name, DataSource)
* @param readDataSourceMap
*/
public void setReadDataSourceMap(Map<String, DataSource> readDataSourceMap) {
this.readDataSourceMap = readDataSourceMap;
}
public void setWriteDataSource(DataSource writeDataSource) {
this.writeDataSource = writeDataSource;
}
@Override
public void afterPropertiesSet() throws Exception {
if(writeDataSource == null) {
throw new IllegalArgumentException("property 'writeDataSource' is required");
}
if(CollectionUtils.isEmpty(readDataSourceMap)) {
throw new IllegalArgumentException("property 'readDataSourceMap' is required");
}
readDataSourceCount = readDataSourceMap.size();
readDataSources = new DataSource[readDataSourceCount];
readDataSourceNames = new String[readDataSourceCount];
int i = 0;
for(Entry<String, DataSource> e : readDataSourceMap.entrySet()) {
readDataSources[i] = e.getValue();
readDataSourceNames[i] = e.getKey();
i++;
}
}
private DataSource determineDataSource() {
if(ReadWriteDataSourceDecision.isChoiceWrite()) {
log.debug("current determine write datasource");
return writeDataSource;
}
if(ReadWriteDataSourceDecision.isChoiceNone()) {
log.debug("no choice read/write, default determine write datasource");
return writeDataSource;
}
return determineReadDataSource();
}
private DataSource determineReadDataSource() {
//按照顺序选择读库
//TODO 算法改进
int index = counter.incrementAndGet() % readDataSourceCount;
if(index < 0) {
index = - index;
}
String dataSourceName = readDataSourceNames[index];
log.error("---------------current determine read datasource : {}", dataSourceName);
return readDataSources[index];
}
@Override
public Connection getConnection() throws SQLException {
return determineDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineDataSource().getConnection(username, password);
}
}
public class ReadWriteDataSourceDecision {
public enum DataSourceType {
write, read;
}
private static final ThreadLocal<DataSourceType> holder = new ThreadLocal<DataSourceType>();
public static void markWrite() {
holder.set(DataSourceType.write);
}
public static void markRead() {
holder.set(DataSourceType.read);
}
public static void reset() {
holder.set(null);
}
public static boolean isChoiceNone() {
return null == holder.get();
}
public static boolean isChoiceWrite() {
return DataSourceType.write == holder.get();
}
public static boolean isChoiceRead() {
return DataSourceType.read == holder.get();
}
}
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.NestedRuntimeException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils;
/**
*
*
* <pre>
*
* 此类实现了两个职责(为了减少类的数量将两个功能合并到一起了):
* 读/写动态数据库选择处理器
* 通过AOP切面实现读/写选择
*
*
* ★★读/写动态数据库选择处理器★★
* 1、首先读取<tx:advice>事务属性配置
*
* 2、对于所有读方法设置 read-only="true" 表示读取操作(以此来判断是选择读还是写库),其他操作都是走写库
* 如<tx:method name="×××" read-only="true"/>
*
* 3、 forceChoiceReadOnWrite用于确定在如果目前是写(即开启了事务),下一步如果是读,
* 是直接参与到写库进行读,还是强制从读库读<br/>
* forceChoiceReadOnWrite:true 表示目前是写,下一步如果是读,强制参与到写事务(即从写库读)
* 这样可以避免写的时候从读库读不到数据
*
* 通过设置事务传播行为:SUPPORTS实现
*
* forceChoiceReadOnWrite:false 表示不管当前事务是写/读,都强制从读库获取数据
* 通过设置事务传播行为:NOT_SUPPORTS实现(连接是尽快释放)
* 『此处借助了 NOT_SUPPORTS会挂起之前的事务进行操作 然后再恢复之前事务完成的』
* 4、配置方式
* <bean id="readWriteDataSourceTransactionProcessor" class="cn.javass.common.datasource.ReadWriteDataSourceProcessor">
* <property name="forceChoiceReadWhenWrite" value="false"/>
* </bean>
*
* 5、目前只适用于<tx:advice>情况 TODO 支持@Transactional注解事务
*
*
*
* ★★通过AOP切面实现读/写库选择★★
*
* 1、首先将当前方法 与 根据之前【读/写动态数据库选择处理器】 提取的读库方法 进行匹配
*
* 2、如果匹配,说明是读取数据:
* 2.1、如果forceChoiceReadOnWrite:true,即强制走读库
* 2.2、如果之前是写操作且forceChoiceReadOnWrite:false,将从写库进行读取
* 2.3、否则,到读库进行读取数据
*
* 3、如果不匹配,说明默认将使用写库进行操作
*
* 4、配置方式
* <aop:aspect order="-2147483648" ref="readWriteDataSourceTransactionProcessor">
* <aop:around pointcut-ref="txPointcut" method="determineReadOrWriteDB"/>
* </aop:aspect>
* 4.1、此处order = Integer.MIN_VALUE 即最高的优先级(请参考http://jinnianshilongnian.iteye.com/blog/1423489)
* 4.2、切入点:txPointcut 和 实施事务的切入点一样
* 4.3、determineReadOrWriteDB方法用于决策是走读/写库的,请参考
* @see cn.javass.common.datasource.ReadWriteDataSourceDecision
* @see cn.javass.common.datasource.ReadWriteDataSource
*
* </pre>
*
*
*/
public class ReadWriteDataSourceProcessor implements BeanPostProcessor {
private static final Logger log = LoggerFactory.getLogger(ReadWriteDataSourceProcessor.class);
private boolean forceChoiceReadWhenWrite = false;
private Map<String, Boolean> readMethodMap = new HashMap<String, Boolean>();
/**
* 当之前操作是写的时候,是否强制从从库读
* 默认(false) 当之前操作是写,默认强制从写库读
* @param forceReadOnWrite
*/
public void setForceChoiceReadWhenWrite(boolean forceChoiceReadWhenWrite) {
this.forceChoiceReadWhenWrite = forceChoiceReadWhenWrite;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if(!(bean instanceof NameMatchTransactionAttributeSource)) {
return bean;
}
try {
NameMatchTransactionAttributeSource transactionAttributeSource = (NameMatchTransactionAttributeSource)bean;
Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");
nameMapField.setAccessible(true);
Map<String, TransactionAttribute> nameMap = (Map<String, TransactionAttribute>) nameMapField.get(transactionAttributeSource);
for(Entry<String, TransactionAttribute> entry : nameMap.entrySet()) {
RuleBasedTransactionAttribute attr = (RuleBasedTransactionAttribute)entry.getValue();
//仅对read-only的处理
if(!attr.isReadOnly()) {
continue;
}
String methodName = entry.getKey();
Boolean isForceChoiceRead = Boolean.FALSE;
if(forceChoiceReadWhenWrite) {
//不管之前操作是写,默认强制从读库读 (设置为NOT_SUPPORTED即可)
//NOT_SUPPORTED会挂起之前的事务
attr.setPropagationBehavior(Propagation.NOT_SUPPORTED.value());
isForceChoiceRead = Boolean.TRUE;
} else {
//否则 设置为SUPPORTS(这样可以参与到写事务)
attr.setPropagationBehavior(Propagation.SUPPORTS.value());
}
log.debug("read/write transaction process method:{} force read:{}", methodName, isForceChoiceRead);
readMethodMap.put(methodName, isForceChoiceRead);
}
} catch (Exception e) {
throw new ReadWriteDataSourceTransactionException("process read/write transaction error", e);
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private class ReadWriteDataSourceTransactionException extends NestedRuntimeException {
public ReadWriteDataSourceTransactionException(String message, Throwable cause) {
super(message, cause);
}
}
public Object determineReadOrWriteDB(ProceedingJoinPoint pjp) throws Throwable {
Class targetClass=pjp.getTarget().getClass();
String methodName=pjp.getSignature().getName();
Method[] methods=targetClass.getDeclaredMethods();
for(Method method:methods){
if(method.getName().equals(methodName)){
Transactional transactional=method.getAnnotation(Transactional.class);
if(transactional!=null){
if(transactional.readOnly()){
ReadWriteDataSourceDecision.markRead();
}else{
ReadWriteDataSourceDecision.markWrite();
}
}else{
if (isChoiceReadDB(pjp.getSignature().getName())) {
ReadWriteDataSourceDecision.markRead();
} else {
ReadWriteDataSourceDecision.markWrite();
}
}
}
}
try {
return pjp.proceed();
} finally {
ReadWriteDataSourceDecision.reset();
}
}
private boolean isChoiceReadDB(String methodName) {
String bestNameMatch = null;
for (String mappedName : this.readMethodMap.keySet()) {
if (isMatch(methodName, mappedName)) {
bestNameMatch = mappedName;
break;
}
}
Boolean isForceChoiceRead = readMethodMap.get(bestNameMatch);
//表示强制选择 读 库
if(isForceChoiceRead == Boolean.TRUE) {
return true;
}
//如果之前选择了写库 现在还选择 写库
if(ReadWriteDataSourceDecision.isChoiceWrite()) {
return false;
}
//表示应该选择读库
if(isForceChoiceRead != null) {
return true;
}
//默认选择 写库
return false;
}
protected boolean isMatch(String methodName, String mappedName) {
return PatternMatchUtils.simpleMatch(mappedName, methodName);
}
}