在多租户实现中,在单个事务期间切换数据源

5
我是一名有用的助手,可以为您翻译文本。

我已经苦苦挣扎了几天,试图让它工作,但似乎找不到解决方案。这就是为什么我想在这里问一下。

简短版本

我有一个使用Spring boot、Spring Data JPA和Hibernate的多租户实现,这个实现非常好用。但现在我想实现一个功能,在单个事务中切换数据库(数据源)。例如,我在我的服务类中使用类似的代码

@Autowired
private CustomRepository customRepository;

@Autorwired
private CustomTenantIdentifierResolver customResolver;

@Transactional
public Custom getCustom(String name) {

  // Set the datasource to "one";
  this.customResolver.setIdentifier("one");
  Custom result = this.customRepository.findOneByName(name);

  //If the result is null, switch datasource to default and try again
  this.customResolver.setIdentifier("default");
  result = this.customRepository.findOneByName(name);

  return result;
}

问题是,我的数据源没有切换。第二个请求使用相同的源。我想我在这里做错了什么。
正确的方法是如何在单个事务中切换数据源?
编辑(07-06-2016) 由于我注意到为单个事务切换数据源不起作用,所以我会添加后续内容。
是否可能在单个用户请求的两个事务之间切换数据源?如果可以,正确的方法是什么?
长版本 在继续之前,我想提到我的多租户实现是基于此博客上提供的教程。
现在,我的目标是在动态选择的数据源(由自定义标识符选择)无法找到结果时将默认数据源用作回退。所有这些都需要在单个用户请求中完成。解决方案使用单个或多个事务注释方法都没有区别。

到目前为止,我尝试了几件事情,其中之一已经描述过了,另一个包括使用多个事务管理器。该实现使用一个配置文件创建两个事务管理器bean,每个bean使用不同的数据源。

@Configuration
@EnableTransactionManagement
public class TransactionConfig {

  @Autowired
  private EntityManagerFactory entityManagerFactory;

  @Autowired
  private DataSourceProvider dataSourceProvider;

  @Bean(name = "defaultTransactionManager")
  public PlatformTransactionManager defaultTransactionManager() {
    JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
    jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
    jpaTransactionManager.setDataSource(dataSourceProvider.getDefaultDataSource());
    jpaTransactionManager.afterPropertiesSet();
    return jpaTransactionManager;
  }

  @Bean(name = "dynamicTransactionManager")
  public PlatformTransactionManager dynamicTransactionManager() {
    JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
    jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
    jpaTransactionManager.afterPropertiesSet();
    return jpaTransactionManager;
  }

}

接下来,我将服务方法分成了两个独立的方法,并添加了@Transactional注释,包括正确的bean名称。

@Transactional("dynamicTransactionManager")
public Custom getDynamicCustom(String name) {
  ...stuff...
}

@Transactional("defaultTransactionManager")
public Custom getDefaultCustom(String name) {
  ...stuff...
}

但是这并没有什么区别,第一个数据源仍然被用于第二个方法调用(应该使用默认事务管理器)。
我希望有人可以帮助我找到解决方案。
提前致谢。

你最终找到了解决方案吗? - akokskis
不,对不起。一旦Spring从数据源请求连接,它将在整个事务/会话期间持续使用相同的连接。然而,有一个可能的解决方案,涉及使用多个EntityManagers,虽然我还没有测试过,也不确定是否允许您动态切换EntityManagers。通过快速搜索谷歌,我找到了这个网址:http://fabiomaffioletti.me/blog/2014/04/15/distributed-transactions-multiple-databases-spring-boot-spring-data-jpa-atomikos/ - Robin Hermans
怎么样在新线程中执行第二个事务?因为事务关联线程。 - chou
4个回答

2
Spring提供了一种名为AbstractRoutingDatasource的DataSource变体。它可以用来代替标准的DataSource实现,并在运行时启用确定每个操作使用哪个具体DataSource的机制。您只需要扩展它并提供一个抽象的determineCurrentLookupKey方法的实现即可。
请记住,determineCurrentLookupKey方法将在每个TransactionsManager请求连接时调用。因此,如果您想切换DataSource,只需打开新事务即可。
您可以在这里找到示例: http://fedulov.website/2015/10/14/dynamic-datasource-routing-with-spring/

1
你好,仅提供链接的答案并不适合在StackOverflow上。请在您的回答中添加更多细节。 - Chait

0

你不能仅仅将一个事务移动到另一个数据源中。虽然有分布式(或XA)事务的概念,但它由单独的事务(在不同的数据源中)组成,这些事务被视为单个(分布式)事务的一部分。


我有点预料到在事务期间尝试切换数据源会导致问题。但是你知道吗,如果我在单个用户请求中使用两个单独的事务,是否可以切换数据源? - Robin Hermans
这听起来并不完全不可行,但我对实现不够熟悉,无法提供帮助。 - Kayaman

0

我不知道是否可能,但我认为您应该尝试避免在交易过程中切换源,原因如下:

如果第二个请求期间发生错误,您将希望回滚整个事务,这意味着需要切换回旧的源。为了能够做到这一点,您需要保持与那个旧源的开放连接:当交易完成时,您需要向那个旧源确认交易。

我建议重新考虑是否真的需要这样做,无论是否可能。


有道理。但是在单个用户请求中使用两个单独的事务来切换数据源是否可能? - Robin Hermans
1
理论上是的,我对Spring框架的了解几乎为零。它可能被(部分)限制为每个请求一个事务。如果第二个事务失败,如何通知客户端第一部分成功,第二部分未成功。让客户端进行两个单独的请求,每个请求都有自己的责任,这样不是更好吗?我的意思是:退一步看功能,并想想你首先是如何陷入这种情况的。 - Alfons

0
同样的问题在这里 - MultitenantDataSource的工作非常好,但是如果你想在同一个请求中切换租户,你会一直得到从第一次解析DataSource时得到的结果。
在子类化的AbstractRoutingDataSource中,determineCurrentLookupKey()方法似乎只会在每个会话/请求中被调用一次。
我尝试通过ApplicationContext多次获取Repository Bean(在更改LookupKey后),但没有帮助。
我的解决方法在REST应用程序中: 我使用RestTemplate调用自己的服务 - 这样每次都能正确解析DataSource。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接