Перейти к основному содержимому

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

· 6 мин. чтения

1. Обзор

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

2. Сущности

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

Вот первая сущность пользователя :

package com.foreach.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;
}

А вот и вторая сущность, Product :

package com.foreach.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.foreach.multipledb.dao.user;

public interface UserRepository
extends JpaRepository<User, Integer> { }

и ProductRepository :

package com.foreach.multipledb.dao.product;

public interface ProductRepository
extends JpaRepository<Product, Integer> { }

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

4. Настройте JPA с Java

Теперь мы перейдем к фактической конфигурации Spring. Сначала мы настроим два класса конфигурации — один для пользователя , а другой для продукта .

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

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

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

@Configuration
@PropertySource({ "classpath:persistence-multiple-db.properties" })
@EnableJpaRepositories(
basePackages = "com.foreach.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.foreach.multipledb.model.user" });

HibernateJpaVendorAdapter vendorAdapter
= new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> 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 в качестве основного TransactionManager , аннотируя определение bean-компонента с помощью @Primary . Это полезно всякий раз, когда мы собираемся явно или неявно внедрить диспетчер транзакций, не указывая, какой из них по имени.

Далее давайте обсудим PersistenceProductConfiguration , где мы определяем похожие bean-компоненты:

@Configuration
@PropertySource({ "classpath:persistence-multiple-db.properties" })
@EnableJpaRepositories(
basePackages = "com.foreach.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.foreach.multipledb.model.product" });

HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> 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("john@test.com");
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("john@test.com");
user1.setAge(20);
user1 = userRepository.save(user1);
assertNotNull(userRepository.findOne(user1.getId()));

User user2 = new User();
user2.setName("Tom");
user2.setEmail("john@test.com");
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

Spring Boot может упростить приведенную выше конфигурацию.

По умолчанию 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.foreach.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.foreach.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 автоматически позаботится обо всем остальном.

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

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

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

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

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.

Помимо этого, конфигурация bean-компонентов Transaction Manager и Entity Manager такая же, как и в стандартном приложении Spring.

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

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

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