MySQL讀寫分離事務策略實現
阿新 • • 發佈:2019-02-06
事務策略
之前的實現,是將通過方法名匹配
而不是使用事務策略中的定義
可以使用事務管理策略中的規則匹配
需要修改兩個地方
定義切面
定義資料來源的AOP切面
定義切面
<!-- 定義AOP切面處理器 -->
<bean class="cn.itcast.usermanage.spring.DataSourceAspect" id="dataSourceAspect">
<!-- 指定事務策略 -->
<property name="txAdvice" ref="txAdvice" />
<!-- 指定slave方法的字首(非必須) -->
<property name="slaveMethodStart" value="query,find,get" />
</bean>
DataSourceAspect
定義資料來源的AOP切面
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils;
/**
* 定義資料來源的AOP切面,該類控制了使用Master還是Slave。
*
* 如果事務管理中配置了事務策略,則採用配置的事務策略中的標記了ReadOnly的方法是用Slave,其它使用Master。
*
* 如果沒有配置事務管理的策略,則採用方法名匹配的原則,以query、find、get開頭方法用Slave,其它用Master。
*
*/
public class DataSourceAspect {
private List<String> slaveMethodPattern = new ArrayList<String>();
private static final String[] defaultSlaveMethodStart = new String[]{ "query", "find", "get" };
private String[] slaveMethodStart;
/**
* 讀取事務管理中的策略
*
* @param txAdvice
* @throws Exception
*/
@SuppressWarnings("unchecked")
public void setTxAdvice(TransactionInterceptor txAdvice) throws Exception {
if (txAdvice == null) {
// 沒有配置事務管理策略
return;
}
//從txAdvice獲取到策略配置資訊
TransactionAttributeSource transactionAttributeSource = txAdvice.getTransactionAttributeSource();
if (!(transactionAttributeSource instanceof NameMatchTransactionAttributeSource)) {
return;
}
//使用反射技術獲取到NameMatchTransactionAttributeSource物件中的nameMap屬性值
NameMatchTransactionAttributeSource matchTransactionAttributeSource = (NameMatchTransactionAttributeSource) transactionAttributeSource;
Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class, "nameMap");
nameMapField.setAccessible(true); //設定該欄位可訪問
//獲取nameMap的值
Map<String, TransactionAttribute> map = (Map<String, TransactionAttribute>) nameMapField.get(matchTransactionAttributeSource);
//遍歷nameMap
for (Map.Entry<String, TransactionAttribute> entry : map.entrySet()) {
if (!entry.getValue().isReadOnly()) {//判斷之後定義了ReadOnly的策略才加入到slaveMethodPattern
continue;
}
slaveMethodPattern.add(entry.getKey());
}
}
/**
* 在進入Service方法之前執行
*
* @param point 切面物件
*/
public void before(JoinPoint point) {
// 獲取到當前執行的方法名
String methodName = point.getSignature().getName();
boolean isSlave = false;
if (slaveMethodPattern.isEmpty()) {
// 當前Spring容器中沒有配置事務策略,採用方法名匹配方式
isSlave = isSlave(methodName);
} else {
// 使用策略規則匹配
for (String mappedName : slaveMethodPattern) {
if (isMatch(methodName, mappedName)) {
isSlave = true;
break;
}
}
}
if (isSlave) {
// 標記為讀庫
DynamicDataSourceHolder.markSlave();
} else {
// 標記為寫庫
DynamicDataSourceHolder.markMaster();
}
}
/**
* 判斷是否為讀庫
*
* @param methodName
* @return
*/
private Boolean isSlave(String methodName) {
// 方法名以query、find、get開頭的方法名走從庫
return StringUtils.startsWithAny(methodName, getSlaveMethodStart());
}
/**
* 萬用字元匹配
*
* Return if the given method name matches the mapped name.
* <p>
* The default implementation checks for "xxx*", "*xxx" and "*xxx*" matches, as well as direct
* equality. Can be overridden in subclasses.
*
* @param methodName the method name of the class
* @param mappedName the name in the descriptor
* @return if the names match
* @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
*/
protected boolean isMatch(String methodName, String mappedName) {
return PatternMatchUtils.simpleMatch(mappedName, methodName);
}
/**
* 使用者指定slave的方法名字首
* @param slaveMethodStart
*/
public void setSlaveMethodStart(String[] slaveMethodStart) {
this.slaveMethodStart = slaveMethodStart;
}
public String[] getSlaveMethodStart() {
if(this.slaveMethodStart == null){
// 沒有指定,使用預設
return defaultSlaveMethodStart;
}
return slaveMethodStart;
}
}