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, поэтому его легко импортировать и запускать как есть.