package me.test.db.router;
import java.lang.reflect.Method;
import java.util.Stack;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* 在 Spring Transaction AOP 之前,将路由数据源的key存设置一下。
*
* <p>
* 数据源key的查找及处理流程如下:
* <ol>
* <li>查找数据源 key
* <ol>
* <li>如果事务方法上有注解 {@link DataSouceKey},则计算该注解的SpEL表达式,将计算结果以String类型作为数据源key</li>
* <li>如果 keyParamName 非空,且class文件保留有debug信息,则以事务方法的参数名为 keyParamName 的值作为数据源key</li>
* <li>如果 using1stParamAsDefaultKey 为 true,则将事务方法的第一个参数的值作为数据源key</li>
* </ol>
* <li>
* <li>如果找到了数据源key,则且但与之前启用事务的key不一致,则根据 throwExceptionWhenCrossDb 的值决定是抛出 {@link CrossDbTransNotSupportedException}
* 异常还是无视。</li>
* <li>如果还没找到数据源key,且allowNullKey=false则抛出 {@link DataSouceKeyNotFoundException} 异常。
* 否则将null作为key,如果AbstractRoutingDataSource配置了defaultTargetDataSource,将使用默认数据源。</li>
* <ol>
* </p>
*/
public class DataSourceKeyAdvice implements MethodInterceptor {
private Logger logger = LoggerFactory.getLogger(DataSourceKeyAdvice.class);
private DataSourceKeyResolver keyResolver;
private DataSourceKeyMapper keyMapper;
/** 跨DB时是否抛出异常 */
private boolean throwExceptionWhenCrossDb = true;
/** 是否允许空key */
private boolean allowNullKey = true;
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
@SuppressWarnings("unchecked")
Stack<String> keyStack = (Stack<String>) TransactionSynchronizationManager.getResource(RoutingDataSourceImpl.DATASOURCE_RSC_KEY);
if (keyStack == null) {
keyStack = new Stack<String>();
TransactionSynchronizationManager.bindResource(RoutingDataSourceImpl.DATASOURCE_RSC_KEY, keyStack);
}
Method targetMethod = invocation.getMethod();
String key = keyResolver.resolveKey(invocation.getThis(), targetMethod, invocation.getArguments());
if (keyMapper != null) {
key = keyMapper.mapping(key);
}
// 防止跨数据库进行事务
if (key != null && throwExceptionWhenCrossDb) {
for (int i = 0; i < keyStack.size(); i++) {
String iKey = keyStack.get(i);
if (iKey != null && !iKey.equals(key)) {
throw new CrossDbTransNotSupportedException();
}
}
}
if (key == null && !allowNullKey) {
throw new DataSouceKeyNotFoundException("Could not determin data source key for method : " + targetMethod);
}
if (logger.isDebugEnabled()) {
logger.debug("using '{}' as data source key for method : {}", key, targetMethod);
}
keyStack.push(key);
try {
return invocation.proceed();
} finally {
keyStack.pop();
}
}
public boolean isAllowNullKey() {
return allowNullKey;
}
public void setAllowNullKey(boolean allowNullKey) {
this.allowNullKey = allowNullKey;
}
public boolean isThrowExceptionWhenCrossDb() {
return throwExceptionWhenCrossDb;
}
public void setThrowExceptionWhenCrossDb(boolean throwExceptionWhenCrossDb) {
this.throwExceptionWhenCrossDb = throwExceptionWhenCrossDb;
}
public void setKeyResolver(DataSourceKeyResolver keyResolver) {
this.keyResolver = keyResolver;
}
public void setKeyMapper(DataSourceKeyMapper keyMapper) {
this.keyMapper = keyMapper;
}
}