Настройте два источника данных в Spring boot очень чистым способом

Хорошо, я прочитал довольно много сообщений об этом и прочитал документацию по весенней загрузке по этому вопросу, но так и не получил ответа, который является более чистым подходом.

Вот мой вариант использования: у нас есть около 100-200 объектов оракула, и мы используем интерфейс JPARepository для их запроса. Теперь нам нужно убедиться, что read db используется для вызовов чтения, а write db должен использоваться для любых операций записи.

У нас есть приложение весенней загрузки, использующее источник данных HikariCP, и мы настроили его с помощью @EnableTransactionManagement, @EnableJpaRepositories, передавая ссылку на диспетчер сущностей, диспетчер транзакций и базовые пакеты для сканирования.

Я создал два файла конфигурации, один с ReadConfiguration и один с WriteConfiguration. Теперь проблема в том, что у нас есть код в стандартном объектно-ориентированном стиле, где у нас есть уровень сервиса и репозитория. Разные сервисы внедряют разные репозитории. Каждый интерфейс репозитория расширяет JpaRepository, и этот интерфейс автоматически подключается к множеству классов обслуживания.

Чего я хочу добиться, так это использовать тот же уровень репозитория, но каким-то образом уровень репозитория должен автоматически знать, что если это вызов для чтения, то используйте источник данных для чтения, а если это вызов для записи, используйте источник данных для записи.

Одним из решений является использование какого-либо способа взлома прокси-реализации репозитория, чтобы иметь некоторую логику, чтобы увидеть, является ли это вызовом для чтения, а затем использовать этот источник данных для чтения, если это вызов для записи, а затем использовать источник данных для записи. Кто-нибудь решил это таким способом? По сути, мне нужно перенаправить вызов на источник данных в зависимости от вызова метода. Если метод выполняет некоторую операцию чтения, мне нужно убедиться, что он использует источник данных для чтения или использует источник данных для записи.

Может ли кто-нибудь указать в каком-то направлении, как добиться такой архитектуры без написания нового уровня репозитория для чтения и записи.

Спасибо.


person User5817351    schedule 13.04.2017    source источник
comment
@ Оливер Гирке, у вас есть какие-нибудь указания на это, спасибо!   -  person User5817351    schedule 13.04.2017


Ответы (3)


Я думаю, вы можете использовать AbstractRoutingDataSource, как указано в этом репо. https://github.com/kwon37xi/replication-datasource

При таком подходе вы можете добавить метод @Transactional с дополнительным свойством, чтобы указать, читается он или пишется. @Transactional(только для чтения = истина|ложь)

person Praneeth Ramesh    schedule 13.04.2017
comment
Посмотрев на это бегло, таким образом мне нужно было бы обновить примерно 100-200 сервисных слоев, чтобы добавить аннотацию транзакции, чего я пытаюсь избежать. Скорее, я бы добавил пару новых классов, которые могут выполнить свою работу. Также мы используем источник данных HikariCP, но не уверены, смогу ли я использовать вышеуказанные методы для разветвления классов Spring JDBC. - person User5817351; 14.04.2017
comment
Также в некоторых случаях один метод службы выполняет вызов чтения, а затем вызов записи. Так что в этом случае мне нужно будет использовать запись источника данных для таких методов службы. - person User5817351; 14.04.2017

Я не думаю, что это на самом деле возможно (см. ниже), но это подход, чтобы хотя бы приблизиться.

Я бы начал с кода LazyConnectionDataSource, потому что из-за ваших требований вы не должны получать соединение, пока не узнаете, что вы собираетесь с ним делать.

Дайте ему ссылку на сеанс, поэтому, когда ему действительно нужен Connection, он проверяет сеанс, чтобы увидеть, не загрязнен ли он, и принять решение на основе того, что фактически использовалось DataSource.

Теперь о причине, почему я не думаю, что это действительно сработает. В обычном случае использования транзакция состоит из:

  1. чтение некоторых данных

  2. внесение некоторых изменений на его основе

  3. сохранение этих изменений.

Эти шаги происходят строго последовательно, и хотя у нас есть такие типы, как Future в Java, мы не можем заглянуть в будущее и, следовательно, не можем на первом шаге решить, будет ли третий шаг.

person Jens Schauder    schedule 14.04.2017
comment
Спасибо за ваш ответ. Тогда я предполагаю, что мне нужно будет зарегистрировать другой диспетчер сущностей для другого репозитория, т.е. таким образом, у меня будет источник данных записи (источник данных записи также может выполнять чтение), определенный для репозиториев, которые выполняют любую операцию записи, и источник данных чтения для репозиториев, которые выполняют чтение источника данных . Думаю, это было бы более легкой задачей. - person User5817351; 14.04.2017

Я предполагаю, что это можно рассматривать как подход с несколькими арендаторами, один арендатор будет только для чтения, другой арендатор будет запись.

Шаги по настройке уровня сохраняемости для поддержки нескольких арендаторов включают в себя:

  • 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
  }
}
  • Компоненты источников данных

MultiTenantJpaConfiguration.java

 ...
 public class MultiTenantJpaConfiguration {
 ...
   @Bean(name = "dataSourcesDvdRental" )
   public Map<String, DataSource> dataSourcesDvdRental() {
       ...
   }
 ...
 }
  • Компонент фабрики менеджера сущностей

MultiTenantJpaConfiguration.java

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

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

   @Bean
   public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(MultiTenantConnectionProvider multiTenantConnectionProvider,
     CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
       ...  
   }
 ...
 }
  • Компонент менеджера транзакций

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(...);
...

Установка tenantId может быть выполнена в фильтре сервлета/перехватчике Spring MVC/потоке, который будет выполнять операцию JPA и т. д.

Более подробную информацию о многопользовательском подходе можно найти в моем блоге по адресу http://tech.asimio.net/2017/01/17/Multitenant-applications-using-Spring-Boot-JPA-Hibernate-and-Postgres.html

person ootero    schedule 14.04.2017