AbstractRoutingDataSource不会更改连接

时间:2016-06-01 06:42:33

标签: java spring datasource

我使用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更改数据源。

3 个答案:

答案 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