一、问题背景
项目中原来没有使用多数据源的需求,后期需要使用多数据源。
多数据源的实现主要就是通过 实现一个类,继承AbstractRoutingDataSource
,并重写determineCurrentLookupKey
方法,加上注解、切面,实现根据注解配置指定方法使用的多数据源。
本来实现的好好的,单个方法内使用指定的某一个数据源也走通了,但有个需求需要在指定的方法内,需要访问数据库A的某个表,然后再访问B的某个表。
二、过程
首先是搜了半天百度,找到了这么一篇文章
jpa+AbstractRoutingDataSource+Transactional数据源切换失效-CSDN博客
https://blog.csdn.net/qichangleixin/article/details/117407676
有些相似,但是不完全相似。但提供了一个点就是事务。搜索发现Jpa在进行查询的时候,似乎会开启一个默认的事务。于是在相关的业务代码上加上注解@Transactional(propagation = Propagation.NOT_SUPPORTED)
但是并没有效果,方法还是没法实现两个数据源的切换。
于是只能顺着源码一路往下断点。发现两次查询的分歧点在于
public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImplementor {
private Connection acquireConnectionIfNeeded() {
if ( physicalConnection == null ) {
// todo : is this the right place for these observer calls?
try {
physicalConnection = jdbcConnectionAccess.obtainConnection();
}
catch (SQLException e) {
throw sqlExceptionHelper.convert( e, "Unable to acquire JDBC Connection" );
}
finally {
observer.jdbcConnectionAcquisitionEnd( physicalConnection );
}
}
return physicalConnection;
}
}
第二次请求进来时,会检查是不是已经创建过连接,如果创建过,会使用原有的,进而导致无法进入到
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
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;
}
}
最终导致无法切换动态数据源。
然后又是一通搜
找到了一篇文章
Spring Data JPA 原理与实战第十一天 Session相关、CompletableFuture、LazyInitializationException_org.hibernate.resource.jdbc.spi.physicalconnection-CSDN博客
https://blog.csdn.net/fegus/article/details/124895552
提到可以修改hibernate.connection.handling_mode
调整处理物理连接的模式。
DELAYED_ACQUISITION_AND_HOLD:延迟获取,一直保持连接到 Session 关闭
表示需要的时候再获取连接,需要的时候是指进行 DB 操作的时候,这里主要是指事务打开的时候,就需要获取连接了(因为开启事务的时候要执行“AUTOCOMMIT=0”的操作,所以这里的按需就是指开启事务;我们也可以关闭事务开启的时候改变 AUTOCOMMIT 的行为,那么这个时候的按需就是指执行 DB 操作的时候,不一定开启事务就会获得 DB 的连接);
这种模式导致了在同一个session里,连接会被复用,进而导致无法切换数据源。
于是调整为DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION
DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION:延迟获取,事务执行之后释放。
在事务执行完之后释放连接,同一个事务共享一个连接;
测试代码,当整个查询方法被套上了事务之后,整段代码将只能使用一个数据源,因为中间使用的是同一个连接。
而当为两个查询数据库的方法打上了@Transactional(propagation = Propagation.NOT_SUPPORTED),将两个事务进行分隔之后,就能够正常工作了。