動態(tài)切換主從庫
首先看下AbstractRoutingDataSource類結(jié)構(gòu)删顶,繼承了AbstractDataSource
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
既然是AbstractDataSource,當(dāng)然就是javax.sql.DataSource的子類粤攒,于是我們自然地回去看它的getConnection方法:
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
展開determineTargetDataSource方法
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
這里用到了我們需要進(jìn)行實(shí)現(xiàn)的抽象方法determineCurrentLookupKey()望抽,該方法返回需要使用的DataSource的key值算途,然后根據(jù)這個key從resolvedDataSources這個map里取出對應(yīng)的DataSource,如果找不到隘梨,則用默認(rèn)的resolvedDefaultDataSource程癌。
這里我們應(yīng)創(chuàng)建自己的類,繼承AbstractRoutingDataSource類轴猎,實(shí)現(xiàn)determineCurrentLookupKey方法
public class DataSourceRouting extends AbstractRoutingDataSource {
public static final ThreadLocal<DataSourceType> DATA_SOURCE_TYPE = new ThreadLocal<>();
@Override
protected Object determineCurrentLookupKey() {
return DATA_SOURCE_TYPE.get();
}
}
數(shù)據(jù)源的名稱常量類
public enum DataSourceType {
MASTER,
SLAVE
}
這里我們切換數(shù)據(jù)源的方法是通過在切面嵌莉,使service層使用了@Transaction注解的方法切換成主庫,其他的方法讀取從庫捻脖,在方法運(yùn)行結(jié)束后再切換成從庫
@Aspect
@Slf4j
public class DataSourceAspect {
// @Pointcut注解內(nèi)容為匹配*..service.impl路徑下以ServiceImpl結(jié)尾的文件中所有有參數(shù)列表的函數(shù)
@Pointcut("execution(* *..service.impl.*ServiceImpl.*(..))")
public void pointCut() {
// Do nothing just for pointCut.
}
@Before(value = "pointCut()")
public void before(JoinPoint point) {
Object target = point.getTarget();
Class<?> clazz = target.getClass();
String method = point.getSignature().getName();
log.debug(clazz.getName() + "." + method + "()");
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
Method m = clazz.getMethod(method, parameterTypes);
DataSourceRouting.DATA_SOURCE_TYPE.set(DataSourceType.SLAVE);
if (m != null) {
if (m.isAnnotationPresent(Transactional.class)) {
Transactional transactional = m.getAnnotation(Transactional.class);
setDataSource(transactional);
} else if (clazz.isAnnotationPresent(Transactional.class)) {
Transactional transactional = clazz.getAnnotation(Transactional.class);
setDataSource(transactional);
}
}
log.info("user dataSource:" + DataSourceRouting.DATA_SOURCE_TYPE.get());
} catch (NoSuchMethodException e) {
log.error("", e);
}
}
//方法期間使用主庫之后切換回從庫
@After(value = "pointCut()")
public void after() {
log.debug("remove dataSource:" + DataSourceRouting.DATA_SOURCE_TYPE.get());
DataSourceRouting.DATA_SOURCE_TYPE.remove();
}
private void setDataSource(Transactional transactional) {
DataSourceRouting.DATA_SOURCE_TYPE.set((transactional.propagation() == Propagation.NOT_SUPPORTED
|| transactional.propagation() == Propagation.NEVER || transactional.readOnly()) ?
DataSourceType.SLAVE : DataSourceType.MASTER);
}
}