如何使用自定义DataSource动态连接存储库?

时间:2017-05-18 18:33:49

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

所以我有一个相当标准的Spring Boot应用程序,它使用JavaConfig和JPA来连接服务和存储库。但是,该应用程序的一个非标准方面是需要根据需要启动隔离的云数据库,以便出于法律原因将客户数据分开。

我有一个简单的ClientService注入了一些存储库,我的目标是创建某种工厂,我可以请求特定于每个客户端的此ClientService版本,该版本具有注入所有存储库的自定义DataSource。 Spring Boot通常非常适合用于连接,它使事情变得更加混乱,因为很难看到幕后发生的事情。

最好的方法是什么?我的第一个想法是一个名为ClientServiceFactory的bean,其方法是getClientService(客户端客户端)。我很容易为这个客户端创建自定义DataSource - 困难的部分是我如何返回ClientService的实例,并自动注入所有其他内容,但强制所有存储库bean使用此数据源。自然ClientService将不再是单身人士,而是我会在客户端内部存储地图> ClientService。

非常感谢任何帮助或建议。

2 个答案:

答案 0 :(得分:2)

我认为您需要实施多租户方法。

我在Multi-tenant applications using Spring Boot, JPA, Hibernate and Postgres

上发表了关于此主题的博文

基本上,这是为多租户配置持久层的步骤:

  • Hibernate,JPA和数据源属性:

<强> application.yml

...
multitenancy:
  dvdrental:
    dataSources:
      -
        tenantId: TENANT_01
        url: jdbc:postgresql://172.16.69.133:5432/db_dvdrental
        username: user_dvdrental
        password: changeit
        driverClassName: org.postgresql.Driver
      -
        tenantId: TENANT_02
        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;
...

DvdRentalTenantContext.setTenantId("TENANT_01");
this.actorDao.findOne(...);
...

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

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

答案 1 :(得分:1)

您可能无法直接实现这一目标。由于存储库与EntityManagers紧密耦合,并且EntityManagers与数据源紧密耦合,暗示存储库与数据源紧密耦合。您可能无法在spring启动时创建存储库。

您可以在开始时为不同的数据源创建多个JPARepositorySets。让我们说你需要一个UserRepository。您有2个数据源DS1,DS2。

您可以为两个数据源创建两个实体管理器EM1,EM2。

public LocalContainerEntityManagerFactoryBean em1() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(ds1());
        em.setPackagesToScan(new String[] { "com.pkg1.entities.user" });
        return em;
    }

public LocalContainerEntityManagerFactoryBean em2() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(ds2());
        em.setPackagesToScan(new String[] { "com.pkg1.entities.user" });
        return em;
    }

在运行时,取决于客户端,工厂bean可以为您创建存储库,并按如下方式返回存储库。

public UserRepository getClientSpecificUserRepository(Client client){
    SimpleJpaRepository<User, Long> userRepository = null;
    if(client.name.equals("ABC")){
        userRepository = new SimpleJpaRepository<User, Serializable>(
    User.class, em1);
    } else (client.name.equals("ABC")){
        userRepository = new SimpleJpaRepository<User, Serializable>(
    User.class, em2);
    }
    return userRepository;
}

因此,最终在上下文启动时为所有客户创建多个EntityManagers。并且Factory在运行时根据客户端

返回Repository bean