切换DataSource的实现我没看懂
Topic source看源码弄懂了一点,但对执行过程还是有些不理解;
笔记区:
首先,ThreadLocal 存储的只是DataSource的Bean名字
public class RoutingDataSourceContext implements AutoCloseable {
public static final String MASTER_DATASOURCE = "masterDataSource";
public static final String SLAVE_DATASOURCE = "slaveDataSource";
// holds data source key in thread local:
static final ThreadLocal<String> threadLocalDataSourceKey = new ThreadLocal<>();
public static String getDataSourceRoutingKey() {
String key = threadLocalDataSourceKey.get();
return key == null ? MASTER_DATASOURCE : key;
}
public RoutingDataSourceContext(String key) {
threadLocalDataSourceKey.set(key);
}
public void close() {
threadLocalDataSourceKey.remove();
}
}
注入的是标记为@Primary的RoutingDataSource,数据源默认为 masterDataSource,RoutingDataSource
它通过Map关联一组DataSource
public class RoutingDataSourceConfiguration {
@Primary
@Bean
DataSource dataSource(@Autowired @Qualifier(RoutingDataSourceContext.MASTER_DATASOURCE) DataSource masterDataSource,
@Autowired @Qualifier(RoutingDataSourceContext.SLAVE_DATASOURCE) DataSource slaveDataSource) {
var ds = new RoutingDataSource();
ds.setTargetDataSources(Map.of(RoutingDataSourceContext.MASTER_DATASOURCE, masterDataSource,
RoutingDataSourceContext.SLAVE_DATASOURCE, slaveDataSource)); //通过Map关联一组DataSource
ds.setDefaultTargetDataSource(masterDataSource);
System.err.println("RoutingDataSourceConfiguration:1"); //观察执行顺序
return ds;
}
}
class RoutingDataSource extends AbstractRoutingDataSource {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Override
protected Object determineCurrentLookupKey() {
return RoutingDataSourceContext.getDataSourceRoutingKey(); //将获取到 DataSource 的Bean名字给父类
}
@Override
protected DataSource determineTargetDataSource() {
DataSource ds = super.determineTargetDataSource();
logger.info("determin target datasource: {}", ds);
System.err.println("RoutingDataSource:2"); //观察执行顺序
return ds;
}
}
之后一直执行到return RoutingDataSourceContext.getDataSourceRoutingKey();
查看该方法的源码,他拿到了 DataSource 的Bean名字和之前提供的Map,就能调用get方法得到DataSource了
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey); //之前通过Map关联的一组DataSource将会存储在private Map<Object, DataSource> resolvedDataSources
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;
}
存储过程,Map关联的 DataSource 是先赋值给 targetDataSources,再通过这个方法赋值给 resolvedDataSources
@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
Object lookupKey = resolveSpecifiedLookupKey(key);
DataSource dataSource = resolveSpecifiedDataSource(value);
this.resolvedDataSources.put(lookupKey, dataSource);
});
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
问题区:
为什么 RoutingDataSource.determineTargetDataSource() 会执行了2次,var ds = new RoutingDataSource();
这句代码只不过是执行完了构造方法就退出了,不应该是导致这个问题的原因
RoutingDataSourceConfiguration:1
2020-11-18 00:59:11.771 INFO 5124 --- [ restartedMain] c.i.learnjava.config.RoutingDataSource : determin target datasource: HikariDataSource (null)
RoutingDataSource:2
2020-11-18 00:59:11.851 INFO 5124 --- [ restartedMain] com.zaxxer.hikari.HikariDataSource : HikariPool-3 - Starting...
2020-11-18 00:59:12.120 INFO 5124 --- [ restartedMain] com.zaxxer.hikari.pool.PoolBase : HikariPool-3 - Driver does not support get/set network timeout for connections. (feature not supported)
2020-11-18 00:59:12.171 INFO 5124 --- [ restartedMain] com.zaxxer.hikari.HikariDataSource : HikariPool-3 - Start completed.
2020-11-18 00:59:12.706 INFO 5124 --- [ restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2020-11-18 00:59:12.987 INFO 5124 --- [ restartedMain] c.i.learnjava.config.RoutingDataSource : determin target datasource: HikariDataSource (HikariPool-3)
RoutingDataSource:2
2020-11-18 00:59:12.989 INFO 5124 --- [ restartedMain] c.i.learnjava.config.RoutingDataSource : determin target datasource: HikariDataSource (HikariPool-3)
RoutingDataSource:2
然后就是针对这个注解@RoutingWithSlave
的AOP实现切换数据源,他确实改变了存储在threadLocalDataSourceKey
的值,
但我不知道他怎么让RoutingDataSource
类中的所有方法再执行一次,更新原来获取的结果,当/profile被访问的时候;key值由masterDataSource变为slaveDataSource后应该重新执行RoutingDataSource
类中的所有方法才会更新结果,我是这么认为的
@Aspect
@Component
public class RoutingAspect {
@Around("@annotation(routingWithSlave)")
public Object routingWithDataSource(ProceedingJoinPoint joinPoint, RoutingWithSlave routingWithSlave)
throws Throwable {
try (RoutingDataSourceContext ctx = new RoutingDataSourceContext(RoutingDataSourceContext.SLAVE_DATASOURCE)) {
System.err.println("RoutingAspect:3");
return joinPoint.proceed();
}
}
}
@GetMapping("/profile")
@RoutingWithSlave
public ModelAndView profile(HttpSession session) {
User user = (User) session.getAttribute(KEY_USER);
if (user == null) {
return new ModelAndView("redirect:/signin");
}
// 测试是否走slave数据库:
user = userService.getUserByEmail(user.getEmail());
return new ModelAndView("profile.html", Map.of("user", user));
}
理解的关键在于ThreadLocal
在进行jdbctemplate时,肯定要获取数据源,当前线程会先调用RoutingDataSourceContext的getDataSourceRoutingKey()拿到查询的key来决定选用哪个数据源。
在这之前,由于AspectJ调用了RoutingDataSourceContext的构造方法,并且调用了向threadLocalDataSourceKey塞入了"slaveDataSource",因此查询的key是由此步确定的。
- 1
用户5273497715
RoutingDataSourceContext 这个类只是在 ThreadLocal 储存了指定DataSource的Bean名字,@Bean("slaveDataSource") DataSource dataSource(...){...},而不是一个 DataSource 对象
客户端代码
自定义的注解 @RoutingWithSlave 的作用也只是调用 RoutingDataSourceContext 的构造方法向 ThreadLocal 储存了字符串 slaveDataSource
当我访问 http://localhost:8080/profile 后,可以看到数据源已经切换为 HikariPool-2 不再是 HikariPool-1