我使用AbstractRoutingDataSource
动态更改数据源,ThreadLocal
设置currentLookupKey。当我每个http请求只使用一个数据源时,它工作得很好。我使用JpaRepository
@Component
@Primary
public class RoutingDataSource extends AbstractRoutingDataSource {
@Autowired
private DatabaseMap databaseMap;
@Override
public void afterPropertiesSet() {
setTargetDataSources(databaseMap.getSourcesMap());
setDefaultTargetDataSource(databaseMap.getSourcesMap().get("DEFAULT"));
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return DatabaseContextHolder.getDatabaseType();
}
}
public class DatabaseContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDatabaseType(String string) {
contextHolder.set(string);
}
public static String getDatabaseType() {
return (String) contextHolder.get();
}
public static void clearDatabaseType() {
contextHolder.remove();
}
}
当我尝试在REST控制器中获取数据时,我只从一个数据库获取数据。
我的REST控制器中的一些代码
DatabaseContextHolder.setDatabaseType("db1");
//here I get data from db1 as expected
//I use JpaRepository
DatabaseContextHolder.clearDatabaseType();
DatabaseContextHolder.setDatabaseType("db2");
//here I should get data from db2 but get from db1
我尝试调试,看起来Spring在http请求中只获取一次数据源。
此方法只调用一次。
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
有没有办法强制Spring更改数据源。
答案 0 :(得分:1)
您的问题可能与交易定界有关。
当您在代码中定义@Transactional
批注时,Spring将代表您创建开始和结束以及提交或回滚(如果需要)事务所需的所有内容。
正如您在doBegin
类的source code中的DataSourceTransactionManager
方法中看到的-其他事务管理器也是如此-Spring获得Connection
时,事务已初始化-这就是方法getConnection
仅被调用一次的原因-,它将在该事务内针对数据库的所有基础操作之间重用该连接(对于ACID保留很有意义)。
因此,如果您需要在同一请求处理中连接到多个数据源,则可以在服务代码中定义不同的方法,每个方法都用@Transactional
注释,并在您更改基础数据源时在调用它们之前需要:
DatabaseContextHolder.setDatabaseType("db1");
// Invoke a service method annotated with @Transactional
// It can use the underlying JpaRepositories that you need
DatabaseContextHolder.clearDatabaseType();
DatabaseContextHolder.setDatabaseType("db2");
// Invoke again another (or the same, what you need) service method
// annotated with @Transactional, You should get data from db2 this time
答案 1 :(得分:0)
我在这里的怀疑是您有一个带有@Transactional
注释的方法。在调用该事务方法之前,首先要指定一个数据源密钥,然后再调用事务方法。在事务方法内部,首先要调用存储库,它可以按预期与设置的数据源查找键一起工作。但是,然后您在事务方法内设置了不同的密钥,并调用了另一个存储库,它仍然使用您第一次设置的密钥。
DataSource
启动时,框架会选择 transaction
,因此如果您使用的是@Transactional
注释,则在该方法内进行的任何切换都是没有用的。 因为数据源将由为@Transactional
注释创建的代理选择。最好的选择是在非事务服务中使用分支逻辑,或者使用TransactionTemplate
代替@Transactional
例如,确保YourRestController
中没有@Transactional
类级别的@Transactional
且没有yourRestControllerMethod
批注,您可以将它们保留在自己的服务。
@RestController
public class YourRestController {
@Autowired
TransactionalService transactional
public void yourRestControllerMethod(){
//set the datasource look up key to A1 and then
transactional.methodA1();
//change datasource look up key to A2 and then
transactional.methodA2();
}
}
@Service
public class TransactionalService {
@Transactional
public void methodA1(){
}
@Transactional
public void methodA2() {
}
}
答案 2 :(得分:0)
我遇到了同样的问题,上述解决方案都不能解决它..但是使我的服务方法 final(在我的 REST 控制器中)
public final Response