基于mysql数据库已经做好了主从,提供出主库和从库的链接
1、实现方式
主要思路是重写spring的AbstractRoutingDataSource类,使用ThreadLocal保存数据源信息,使用aop切service的方法动态切换数据源。
先看下配置文件
MyDataSource是我们重写AbstractRoutingDataSource的determineCurrentLookupKey()方法的类
public class MyDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getRoutingKey();
}
}
dataSource这个bean里面的dataSourceW、dataSourceR是我们配置文件上面配置出的两个数据源的bean。targetDataSources用于运行时通过key获取具体数据源,defaultTargetDataSource必须配置,用于默认或者某些情况下使用,下面要说的事务的获取数据源就依赖这个。然后将该dataSource作为参数用于初始化事务DataSourceTransactionManager和SqlSessionFactoryBean。
然后是保存数据源的ThreadLocal类
public class DataSourceHolder {
private static final ThreadLocal routingKey = new ThreadLocal<>();
public static void setRoutingKey(String key) {
routingKey.set(key);
}
public static String getRoutingKey() {
return routingKey.get();
}
public static void removeRoutingKey() {
routingKey.remove();
}
}
有set和get方法用于存放、获取数据源key,remove这个方法也很重要,涉及到后面提到的一个坑。
然后定义一个切点,使用aop动态切入数据源
这里是切的service包及其子包下的所有类,然后就是重要的切面类了
public class DataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
public Object around(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method targetMethod = signature.getMethod();
DataSource dataSource = targetMethod.getAnnotation(DataSource.class);
logger.debug("DataSourceAspect setDataSource targetMethod={}, dataSource={}", targetMethod, dataSource);
if (dataSource == null) {
//兼容之前代码,不设置标签的就会被设置为master。
DynamicDataSourceHolder.putDataSource(DataSourceConstant.MASTER);
} else {
if (dataSource.value().equals(DataSourceConstant.MASTER)) {
DynamicDataSourceHolder.putDataSource(DataSourceConstant.MASTER);
} else if (dataSource.value().equals(DataSourceConstant.SLAVE)) {
DynamicDataSourceHolder.putDataSource(DataSourceConstant.SLAVE);
} else {
DynamicDataSourceHolder.putDataSource(DataSourceConstant.MASTER);
}
}
Object result = null;
try {
result = pjp.proceed();
} catch (Throwable e) {
throw e;
} finally {
//在处理完切点代码后将ThreadLocal值清掉,线程池复用线程会保留上一个线程设置的值,造成混乱
//实际上service多层调用时,里层调用结束时就会将线程数据源清空了,回到外层数据源变成默认数据源,所以将一定要走从数据源的单写个方法
DynamicDataSourceHolder.remove();
}
return result;
}
}
拿到方法的标注数据源的标签后,做一些处理,由于项目一开始没有做主从分离,所以策略上很谨慎,尽量不出问题的配置。
1、如果数据源标签为空没设置的时候,使用写数据源,这样保证安全。
2、使用around代替before切面,因为我们需要在方法结束后将ThreadLocal里面的数据源信息清空,这也是个坑,网上的方法大多数是没有这个步骤的,但是不知道是否还有其他好的办法,可以在评论里面提出。
在相应的service方法上注明数据源,debug看一下就可以使用了
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value() default DataSourceConstant.MASTER;
}
@Override
@DataSource(DataSourceConstant.SLAVE)
public List select() {
return userDAO.select();
}
加在service的实现层即可
到这使用配置方面就ok了,下篇介绍下源码和坑。
https://www.jianshu.com/p/f5a234e21d2a