【问题标题】:Configure two datasource in Spring boot in very clean way以非常干净的方式在 Spring Boot 中配置两个数据源
【发布时间】:2017-09-10 02:26:45
【问题描述】:

好的,我已经阅读了很多关于此的帖子并阅读了有关此的 Spring Boot 文档,但从未真正得到更清洁的方法的答案。

这是我的用例:我们有大约 100-200 个 oracle 实体,我们正在使用 JPARepository 接口来查询它们。现在我们需要确保 read db 用于读取调用,并且 write db 应该用于任何写入。

我们有一个 Spring Boot 应用程序,使用 HikariCP 数据源并使用 @EnableTransactionManagement、@EnableJpaRepositories 传递实体管理器、事务管理器和基础包的引用进行扫描。

我创建了两个配置文件,一个带有 ReadConfiguration,一个带有 WriteConfiguration。现在的问题是,我们有标准 OO 方式的代码,我们有服务和存储库层。不同的服务正在注入各种存储库。每个存储库接口都在扩展 JpaRepository,并且该接口在许多服务类中自动装配。

我想要实现的是使用相同的存储库层,但不知何故,存储库层应该自动知道如果是读取调用,则使用读取数据源,如果是写入调用,则使用写入数据源。

其中一个解决方案是使用某种方式破解存储库的代理实现以具有一些逻辑来查看它是否是读取调用然后使用这个读取数据源如果它是一个写入调用然后使用写入数据源。有没有人以这种方式解决了这个问题?基本上我需要的是根据方法调用将调用路由到数据源。如果方法正在执行一些读取操作,那么我需要确保它使用读取数据源或使用写入数据源。

任何人都可以指出一些方向,说明如何在不编写新的存储库层进行读写的情况下实现这种架构。

谢谢。

【问题讨论】:

  • @Oliver Gierke 你对此有什么建议吗,谢谢!

标签: spring spring-boot spring-data spring-data-jpa hikaricp


【解决方案1】:

我认为你可以使用这个 repo 中提到的 AbstractRoutingDataSource。 https://github.com/kwon37xi/replication-datasource

使用这种方法,您可以添加带有额外属性的@Transactional 方法来指示它是读取还是写入。 @Transactional(readOnly = true|false)

【讨论】:

  • 粗略看了一下,这样我就需要更新100-200服务层来添加事务注释,这是我试图避免的。相反,我会做的是添加几个可以完成工作的新类。此外,我们正在使用 HikariCP 数据源,不确定我是否可以使用上述方法来分叉 Spring JDBC 类。
  • 另外,在某些情况下,一种服务方法是先进行读取调用,然后再进行写入调用。所以在这种情况下,我需要在这些服务方法上使用写入数据源。
【解决方案2】:

我认为这实际上是不可能的(见下文),但这是一种至少可以接近的方法。

我将从LazyConnectionDataSource 的代码开始,因为根据您的要求,在您真正知道要使用它做什么之前,您一定不能获得连接。

给它一个会话的引用,所以当它实际需要Connection 时,它会检查会话,看看它是否脏并根据实际使用的DataSource 来决定。

现在来谈谈我认为这实际上行不通的原因。在正常用例中,事务包括:

  1. 读取一些数据

  2. 在此基础上做一些修改

  3. 保留这些更改。

这些步骤严格按顺序进行,尽管我们在 Java 中有 Future 这样的类型,但我们实际上无法展望未来,因此无法在第一步决定是否会有第三步。

【讨论】:

  • 感谢您的回复。然后我想我需要为不同的存储库注册不同的实体管理器,即这样我将为正在执行任何写入操作的存储库定义写入数据源(写入数据源也可以读取),并为正在执行读取数据源的存储库定义读取数据源.我猜这会更容易。
【解决方案3】:

我的猜测是这可以看作是一种多租户方法,一个租户是readonly,另一个租户是write

为多租户支持配置持久层的步骤包括:

  • Hibernate、JPA 和数据源属性。比如:

application.yml

...
multitenancy:
  dvdrental:
    dataSources:
      -
        tenantId: readonly
        url: jdbc:postgresql://172.16.69.133:5432/db_dvdrental
        username: user_dvdrental
        password: changeit
        driverClassName: org.postgresql.Driver
      -
        tenantId: write
        url: jdbc:postgresql://172.16.69.133:5532/db_dvdrental
        username: user_dvdrental
        password: changeit
        driverClassName: org.postgresql.Driver
...

MultiTenantJpaConfiguration.java

 ...
 @Configuration
 @EnableConfigurationProperties({ MultiTenantDvdRentalProperties.class, JpaProperties.class })
 @ImportResource(locations = { "classpath:applicationContent.xml" })
 @EnableTransactionManagement
 public class MultiTenantJpaConfiguration {

   @Autowired
   private JpaProperties jpaProperties;

   @Autowired
   private MultiTenantDvdRentalProperties multiTenantDvdRentalProperties;
 ...
 }

MultiTenantDvdRentalProperties.java

...
@Configuration
@ConfigurationProperties(prefix = "multitenancy.dvdrental")
public class MultiTenantDvdRentalProperties {

  private List<DataSourceProperties> dataSourcesProps;
  // Getters and Setters

  public static class DataSourceProperties extends org.springframework.boot.autoconfigure.jdbc.DataSourceProperties {

    private String tenantId;
    // Getters and Setters
  }
}
  • 数据源 bean

MultiTenantJpaConfiguration.java

 ...
 public class MultiTenantJpaConfiguration {
 ...
   @Bean(name = "dataSourcesDvdRental" )
   public Map<String, DataSource> dataSourcesDvdRental() {
       ...
   }
 ...
 }
  • 实体管理器工厂 bean

MultiTenantJpaConfiguration.java

 ...
 public class MultiTenantJpaConfiguration {
 ...
   @Bean
   public MultiTenantConnectionProvider multiTenantConnectionProvider() {
       ...
   }

   @Bean
   public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
       ...
   }

   @Bean
   public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(MultiTenantConnectionProvider multiTenantConnectionProvider,
     CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
       ...  
   }
 ...
 }
  • 事务管理器 bean

MultiTenantJpaConfiguration.java

 ...
 public class MultiTenantJpaConfiguration {
 ...
   @Bean
   public EntityManagerFactory entityManagerFactory(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
       ...
   }

   @Bean
   public PlatformTransactionManager txManager(EntityManagerFactory entityManagerFactory) {
       ...
   }
 ...
 }
  • Spring Data JPA 和事务支持配置

applicationContent.xml

...
<jpa:repositories base-package="com.asimio.dvdrental.dao" transaction-manager-ref="txManager" />
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />
...

ActorDao.java

public interface ActorDao extends JpaRepository<Actor, Integer> {
}

根据您的需要,可以这样做:

...
@Autowired
private ActorDao actorDao;
...

// Read feature
...
DvdRentalTenantContext.setTenantId("readonly");
this.actorDao.findOne(...);
...

// Or write
DvdRentalTenantContext.setTenantId("write");
this.actorDao.save(...);
...

可以在 servlet 过滤器/Spring MVC 拦截器/要执行 JPA 操作的线程等中设置tenantId。

有关多租户方法的更多详细信息,请参阅我的博客http://tech.asimio.net/2017/01/17/Multitenant-applications-using-Spring-Boot-JPA-Hibernate-and-Postgres.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-22
    • 2018-06-14
    • 2021-09-24
    • 1970-01-01
    • 2016-01-17
    相关资源
    最近更新 更多