动态切换主从库
首先看下AbstractRoutingDataSource类结构,继承了AbstractDataSource
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
既然是AbstractDataSource,当然就是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;
}
这里用到了我们需要进行实现的抽象方法determineCurrentLookupKey(),该方法返回需要使用的DataSource的key值,然后根据这个key从resolvedDataSources这个map里取出对应的DataSource,如果找不到,则用默认的resolvedDefaultDataSource。
这里我们应创建自己的类,继承AbstractRoutingDataSource类,实现determineCurrentLookupKey方法
public class DataSourceRouting extends AbstractRoutingDataSource {
public static final ThreadLocal DATA_SOURCE_TYPE = new ThreadLocal<>();
@Override
protected Object determineCurrentLookupKey() {
return DATA_SOURCE_TYPE.get();
}
}
数据源的名称常量类
public enum DataSourceType {
MASTER,
SLAVE
}
这里我们切换数据源的方法是通过在切面,使service层使用了@Transaction注解的方法切换成主库,其他的方法读取从库,在方法运行结束后再切换成从库
@Aspect
@Slf4j
public class DataSourceAspect {
// @Pointcut注解内容为匹配*..service.impl路径下以ServiceImpl结尾的文件中所有有参数列表的函数
@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);
}
}