【问题标题】:AbstractRoutingDataSource doesn't change connectionAbstractRoutingDataSource 不会改变连接
【发布时间】:2016-09-30 09:12:07
【问题描述】:

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

【问题讨论】:

  • 即使m面临同样的问题,你有没有得到任何解决方案
  • 很遗憾我没有
  • 嘿@mariusz2108,到目前为止,您有什么解决方案吗?我刚刚遇到了同样的问题。
  • @AnkitSingodia 我猜你想为同一个请求多次切换。您可以编辑问题以显示您正在切换的位置,包括方法中的任何@Transactional 注释吗?

标签: java spring datasource


【解决方案1】:
  • 我的怀疑是你有一个用@Transactional 注释注释的方法。在调用该事务方法之前,您首先指定一个数据源键并调用事务方法。在事务方法中,您首先调用存储库,它使用您设置的数据源查找键按预期工作。但是,您在事务方法中设置不同的密钥,并调用另一个存储库,它仍然使用您第一次设置的密钥。

  • DataSource 将在transaction 启动时由框架选择,因此如果您使用@Transactional 注释,那么您在方法内进行的任何切换都是无用的。 因为数据源将具有由为@Transactional 注释创建的代理选择。最好的选择是在非事务性服务中使用分支逻辑或使用TransactionTemplate 而不是@Transactional

  • 例如,确保YourRestController 在此yourRestControllerMethod 中没有类级别@Transactional 以及@Transactional 注释,您将保留它们以供您使用。

     @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() {

       }

    }

【讨论】:

  • 我们的设置完全相同。我调试了这个问题,它归结为这样一个事实:当第一个事务开始时 -> 数据源密钥被解析 -> hikari 池尝试创建一个新连接,在该连接中它使用预定义(在配置中)数据源映射的映射和获取正确的数据源。但是当新事务开始时->再次解析数据源密钥(如预期的那样)->但是这一次,hikari 池不会尝试获取新连接,而是使用旧连接,因此它从不使用这次是数据源映射。
  • 不,我仍在寻找解决方法 :(
  • 你能添加你的 HikariDataSource 配置和任何相关属性这两个数据源吗?
  • 请在此处查看:docs.google.com/document/d/…
  • getHibernatePropertiesMaster() 中有什么内容?
【解决方案2】:

您的问题可能与事务定界有关。

当您在代码中定义 @Transactional 注释时,Spring 将代表您创建开始和结束所需的所有内容,并在需要时提交或回滚事务。

正如您在DataSourceTransactionManager 类的source code 中的doBegin 方法中看到的那样——这同样适用于其他事务管理器——,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

【讨论】:

  • 问题是即使在完成了第一个事务之后,hikari 池也不会释放连接,并且对另一个新事务也使用相同的连接。
  • 我明白了。您能否在问题中包含有关如何使用 Hikari 配置实际数据源的信息?
  • 请在此处查看:docs.google.com/document/d/…
  • @AnkitSingodia 除非出于任何其他原因需要,否则请您修改DataSourceConfig 中的代码并删除与不同数据源相关的所有Beans 注释(masterDataSource,@ 987654333@ 和 routingDataSource) 和 Hikari 配置?当您初始化实体管理器工厂时,您实际上是在设置其数据源时创建所有必需的东西:em.setDataSource(routingDataSource())。无需将这些对象也声明为 bean,它将在 Spring 上下文中复制。可以试试看吗?
  • 拜托,你能用你的控制器和服务类的源代码完成谷歌文档吗?我认为这会有所帮助。
【解决方案3】:

我遇到了同样的问题,上述解决方案都无法解决它。但是使我的服务方法 final(在我的 REST 控制器中)

public final Response

【讨论】:

    【解决方案4】:

    spring.jpa.open-in-view 设置为false

    【讨论】:

      猜你喜欢
      • 2019-03-04
      • 1970-01-01
      • 2010-09-23
      • 1970-01-01
      • 2017-10-17
      • 2021-10-22
      • 2012-07-20
      • 2012-05-12
      • 1970-01-01
      相关资源
      最近更新 更多