Рубрики
Без рубрики

Spring JPA – Несколько баз данных

Как настроить Spring Data JPA для работы с несколькими отдельными базами данных.

Автор оригинала: Eugen Paraschiv.

1. Обзор

В этом уроке мы реализуем простую конфигурацию Spring для системы JPA Spring Data с несколькими базами данных .

Дальнейшее чтение:

Spring Data JPA – Производные методы удаления

Программная настройка источника данных при весенней загрузке

2. Субъекты

Во – первых, давайте создадим две простые сущности, каждая из которых живет в отдельной базе данных.

Вот первая сущность ” User “:

package com.baeldung.multipledb.model.user;

@Entity
@Table(schema = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String name;

    @Column(unique = true, nullable = false)
    private String email;

    private int age;
}

И вторая сущность – ” Продукт “:

package com.baeldung.multipledb.model.product;

@Entity
@Table(schema = "products")
public class Product {

    @Id
    private int id;

    private String name;

    private double price;
}

Как вы можете видеть, эти две сущности также помещены в независимые пакеты – это будет важно, когда мы перейдем к конфигурации.

3. Репозитории JPA

Далее – давайте взглянем на наши два репозитория JPA – UserRepository :

package com.baeldung.multipledb.dao.user;

public interface UserRepository
  extends JpaRepository { }

И ProductRepository :

package com.baeldung.multipledb.dao.product;

public interface ProductRepository
  extends JpaRepository { }

Еще раз обратите внимание, как мы создали эти два репозитория в разных пакетах.

4. Настройте JPA С Помощью Java

Далее – давайте перейдем к фактической конфигурации пружины. Мы начнем с настройки двух классов конфигурации – один для Пользователя , а другой для Продукта .

В каждом из этих классов конфигурации нам нужно будет определить следующие интерфейсы для User :

  • Источник данных
  • EntityManagerFactory ( userEntityManager )
  • Менеджер транзакций ( userTransactionManager )

Давайте начнем с просмотра конфигурации пользователя:

@Configuration
@PropertySource({ "classpath:persistence-multiple-db.properties" })
@EnableJpaRepositories(
    basePackages = "com.baeldung.multipledb.dao.user", 
    entityManagerFactoryRef = "userEntityManager", 
    transactionManagerRef = "userTransactionManager"
)
public class PersistenceUserConfiguration {
    @Autowired
    private Environment env;
    
    @Bean
    @Primary
    public LocalContainerEntityManagerFactoryBean userEntityManager() {
        LocalContainerEntityManagerFactoryBean em
          = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(userDataSource());
        em.setPackagesToScan(
          new String[] { "com.baeldung.multipledb.model.user" });

        HibernateJpaVendorAdapter vendorAdapter
          = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        HashMap properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto",
          env.getProperty("hibernate.hbm2ddl.auto"));
        properties.put("hibernate.dialect",
          env.getProperty("hibernate.dialect"));
        em.setJpaPropertyMap(properties);

        return em;
    }

    @Primary
    @Bean
    public DataSource userDataSource() {
 
        DriverManagerDataSource dataSource
          = new DriverManagerDataSource();
        dataSource.setDriverClassName(
          env.getProperty("jdbc.driverClassName"));
        dataSource.setUrl(env.getProperty("user.jdbc.url"));
        dataSource.setUsername(env.getProperty("jdbc.user"));
        dataSource.setPassword(env.getProperty("jdbc.pass"));

        return dataSource;
    }

    @Primary
    @Bean
    public PlatformTransactionManager userTransactionManager() {
 
        JpaTransactionManager transactionManager
          = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(
          userEntityManager().getObject());
        return transactionManager;
    }
}

Обратите внимание, как мы используем userTransactionManager в качестве нашего Primary TransactionManager – путем аннотирования определения компонента с помощью @Primary . Это полезно всякий раз, когда мы собираемся неявно или явно ввести диспетчер транзакций, не указывая, какой из них по имени.

Далее давайте обсудим PersistenceProductConfiguration – где мы определяем аналогичные компоненты:

@Configuration
@PropertySource({ "classpath:persistence-multiple-db.properties" })
@EnableJpaRepositories(
    basePackages = "com.baeldung.multipledb.dao.product", 
    entityManagerFactoryRef = "productEntityManager", 
    transactionManagerRef = "productTransactionManager"
)
public class PersistenceProductConfiguration {
    @Autowired
    private Environment env;

    @Bean
    public LocalContainerEntityManagerFactoryBean productEntityManager() {
        LocalContainerEntityManagerFactoryBean em
          = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(productDataSource());
        em.setPackagesToScan(
          new String[] { "com.baeldung.multipledb.model.product" });

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        HashMap properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto",
          env.getProperty("hibernate.hbm2ddl.auto"));
        properties.put("hibernate.dialect",
          env.getProperty("hibernate.dialect"));
        em.setJpaPropertyMap(properties);

        return em;
    }

    @Bean
    public DataSource productDataSource() {
 
        DriverManagerDataSource dataSource
          = new DriverManagerDataSource();
        dataSource.setDriverClassName(
          env.getProperty("jdbc.driverClassName"));
        dataSource.setUrl(env.getProperty("product.jdbc.url"));
        dataSource.setUsername(env.getProperty("jdbc.user"));
        dataSource.setPassword(env.getProperty("jdbc.pass"));

        return dataSource;
    }

    @Bean
    public PlatformTransactionManager productTransactionManager() {
 
        JpaTransactionManager transactionManager
          = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(
          productEntityManager().getObject());
        return transactionManager;
    }
}

5. Простой тест

Наконец, давайте проверим наши конфигурации.

Мы попробуем простой тест, создав экземпляр каждой сущности и убедившись, что она создана, как в следующем примере:

@RunWith(SpringRunner.class)
@SpringBootTest
@EnableTransactionManagement
public class JpaMultipleDBIntegrationTest {
 
    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ProductRepository productRepository;

    @Test
    @Transactional("userTransactionManager")
    public void whenCreatingUser_thenCreated() {
        User user = new User();
        user.setName("John");
        user.setEmail("[email protected]");
        user.setAge(20);
        user = userRepository.save(user);

        assertNotNull(userRepository.findOne(user.getId()));
    }

    @Test
    @Transactional("userTransactionManager")
    public void whenCreatingUsersWithSameEmail_thenRollback() {
        User user1 = new User();
        user1.setName("John");
        user1.setEmail("[email protected]");
        user1.setAge(20);
        user1 = userRepository.save(user1);
        assertNotNull(userRepository.findOne(user1.getId()));

        User user2 = new User();
        user2.setName("Tom");
        user2.setEmail("[email protected]");
        user2.setAge(10);
        try {
            user2 = userRepository.save(user2);
        } catch (DataIntegrityViolationException e) {
        }

        assertNull(userRepository.findOne(user2.getId()));
    }

    @Test
    @Transactional("productTransactionManager")
    public void whenCreatingProduct_thenCreated() {
        Product product = new Product();
        product.setName("Book");
        product.setId(2);
        product.setPrice(20);
        product = productRepository.save(product);

        assertNotNull(productRepository.findOne(product.getId()));
    }
}

6. Несколько баз данных в весенней загрузке

Пружинная загрузка может упростить конфигурацию, описанную выше.

По умолчанию Spring Boot создаст экземпляр по умолчанию DataSource со свойствами конфигурации с префиксом spring.datasource.* :

spring.datasource.jdbcUrl = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]

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

spring.second-datasource.jdbcUrl = [url]
spring.second-datasource.username = [username]
spring.second-datasource.password = [password]

Поскольку мы хотим, чтобы автоконфигурация Spring Boot собирала эти разные свойства (и создавала два разных источника данных ), мы определим два класса конфигурации, аналогичные тем, которые были описаны в предыдущих разделах:

@Configuration
@PropertySource({"classpath:persistence-multiple-db-boot.properties"})
@EnableJpaRepositories(
  basePackages = "com.baeldung.multipledb.dao.user",
  entityManagerFactoryRef = "userEntityManager",
  transactionManagerRef = "userTransactionManager")
public class PersistenceUserAutoConfiguration {
    
    @Primary
    @Bean
    @ConfigurationProperties(prefix="spring.datasource")
    public DataSource userDataSource() {
        return DataSourceBuilder.create().build();
    }
    // userEntityManager bean 

    // userTransactionManager bean
}
@Configuration
@PropertySource({"classpath:persistence-multiple-db-boot.properties"})
@EnableJpaRepositories(
  basePackages = "com.baeldung.multipledb.dao.product", 
  entityManagerFactoryRef = "productEntityManager", 
  transactionManagerRef = "productTransactionManager")
public class PersistenceProductAutoConfiguration {
   
    @Bean
    @ConfigurationProperties(prefix="spring.second-datasource")
    public DataSource productDataSource() {
        return DataSourceBuilder.create().build();
    }
   
    // productEntityManager bean 

    // productTransactionManager bean
}

Мы определили свойства источника данных внутри persistence-multiple-db-boot.properties в соответствии с соглашением об автоматической настройке загрузки.

Интересной частью является аннотирование метода создания компонента источника данных с помощью @ConfigurationProperties . Нам просто нужно указать соответствующий префикс конфигурации . Внутри этого метода мы используем DataSourceBuilder, и Spring Boot автоматически позаботится обо всем остальном.

Но как сконфигурированные свойства вводятся в Источник данных конфигурацию?

При вызове метода build() в DataSourceBuilder он вызовет свой частный bind() метод:

public T build() {
    Class type = getType();
    DataSource result = BeanUtils.instantiateClass(type);
    maybeGetDriverClassName();
    bind(result);
    return (T) result;
}

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

private void bind(DataSource result) {
    ConfigurationPropertySource source = new MapConfigurationPropertySource(this.properties);
    ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
    aliases.addAliases("url", "jdbc-url");
    aliases.addAliases("username", "user");
    Binder binder = new Binder(source.withAliases(aliases));
    binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
}

Хотя нам самим не нужно прикасаться к этому коду, все равно полезно знать, что происходит под капотом автоконфигурации Spring Boot.

Кроме того, конфигурация компонентов диспетчера транзакций и диспетчера сущностей совпадает со стандартным приложением Spring.

7. Заключение

Эта статья была практическим обзором того, как настроить проект Spring Data JPA для использования нескольких баз данных.

полную реализацию этой статьи можно найти в проекте GitHub – это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.