Spring JPA - vários bancos de dados
1. Visão geral
Neste tutorial, implementaremos uma configuração simples do Spring para umSpring Data JPA system with multiple databases.
Leitura adicional:
Spring Data JPA - Métodos de Exclusão Derivados
Aprenda a definir os métodos deleteBy e removeBy dos Dados da Primavera
Configurando uma fonte de dados programaticamente na inicialização do Spring
Aprenda como configurar um Spring Boot DataSource programaticamente, evitando assim o algoritmo de configuração automática de DataSource do Spring Boot.
2. As Entidades
Primeiro - vamos criar duas entidades simples - cada uma vivendo em um banco de dados separado.
Aqui está a primeira entidade “User“:
package com.example.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;
}
E a segunda entidade - “Product“:
package com.example.multipledb.model.product;
@Entity
@Table(schema = "products")
public class Product {
@Id
private int id;
private String name;
private double price;
}
Como você pode ver,the two entities are also placed in independent packages - isso será importante conforme avançamos na configuração.
Leitura adicional:
Spring Data JPA - Métodos de Exclusão Derivados
Aprenda a definir os métodos deleteBy e removeBy dos Dados da Primavera
Configurando uma fonte de dados programaticamente na inicialização do Spring
Aprenda como configurar um Spring Boot DataSource programaticamente, evitando assim o algoritmo de configuração automática de DataSource do Spring Boot.
3. Os Repositórios JPA
A seguir - vamos dar uma olhada em nossos dois repositórios JPA -UserRepository:
package com.example.multipledb.dao.user;
public interface UserRepository
extends JpaRepository { }
EProductRepository:
package com.example.multipledb.dao.product;
public interface ProductRepository
extends JpaRepository { }
Observe, novamente, como criamos esses dois repositórios em pacotes diferentes.
4. Configurar JPA com Java
Em seguida, vamos ver a configuração atual do Spring. We’ll start by setting up 2 configuration classes – one for the User and the other for the Product.
Em cada uma dessas classes de configuração, precisaremos definir as seguintes interfaces paraUser:
-
Fonte de dados
-
EntityManagerFactory (userEntityManager)
-
TransactionManager (userTransactionManager)
Vamos começar observando a configuração do usuário:
@Configuration
@PropertySource({ "classpath:persistence-multiple-db.properties" })
@EnableJpaRepositories(
basePackages = "com.example.multipledb.dao.user",
entityManagerFactoryRef = "userEntityManager",
transactionManagerRef = "userTransactionManager"
)
public class UserConfig {
@Autowired
private Environment env;
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean userEntityManager() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(userDataSource());
em.setPackagesToScan(
new String[] { "com.example.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;
}
}
Observe como estamos usandouserTransactionManager como nossoPrimary TransactionManager - anotando a definição do bean com@Primary. Isso é útil sempre que vamos injetar implícita ou explicitamente o gerenciador de transações sem especificar qual pelo nome.
A seguir, vamos discutirProductConfig - onde definimos beans semelhantes:
@Configuration
@PropertySource({ "classpath:persistence-multiple-db.properties" })
@EnableJpaRepositories(
basePackages = "com.example.multipledb.dao.product",
entityManagerFactoryRef = "productEntityManager",
transactionManagerRef = "productTransactionManager"
)
public class ProductConfig {
@Autowired
private Environment env;
@Bean
public LocalContainerEntityManagerFactoryBean productEntityManager() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(productDataSource());
em.setPackagesToScan(
new String[] { "com.example.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. Teste simples
Finalmente - vamos testar nossas configurações.
Vamos tentar um teste simples criando uma instância de cada entidade e garantir que ela seja criada - como no exemplo a seguir:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { UserConfig.class, ProductConfig.class })
@TransactionConfiguration
public class JPAMultipleDBTest {
@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. Vários bancos de dados no Spring Boot
O Spring Boot pode simplificar a configuração acima.
Por padrão,Spring Boot will instantiate its default DataSource with the configuration properties prefixed by spring.datasource.*:
spring.datasource.jdbcUrl = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]
Agora queremos continuar usando a mesma maneira paraconfigure the second DataSource, but with a different property namespace:
spring.second-datasource.jdbcUrl = [url]
spring.second-datasource.username = [username]
spring.second-datasource.password = [password]
Como queremos que a configuração automática do Spring Boot pegue essas propriedades diferentes (e, na verdade, instancie doisDataSources diferentes), vamos definir 2 classes de configuração semelhantes às das seções anteriores:
@Configuration
@PropertySource({"classpath:persistence-multiple-db-boot.properties"})
@EnableJpaRepositories(
basePackages = "com.example.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.example.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
}
Definimos as propriedades da fonte de dados empersistence-multiple-db-boot.properties de acordo com a convenção de configuração automática de inicialização.
A parte interessante éannotating the data source bean creation method with*@ConfigurationProperties*. Precisamos apenas especificar o prefixo de configuração. correspondente. Dentro deste método, estamos usando umDataSourceBuilder,e Spring Boot cuidará automaticamente de o resto.
Mas como as propriedades configuradas são realmente injetadas na configuraçãoDataSource?
Ao chamar o métodobuild() emDataSourceBuilder, ele chamará seu métodobind() privado:
public T build() {
Class extends DataSource> type = getType();
DataSource result = BeanUtils.instantiateClass(type);
maybeGetDriverClassName();
bind(result);
return (T) result;
}
Este método privado executa grande parte da mágica da configuração automática, vinculando a configuração resolvida à instânciaDataSource real:
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));
}
Embora não tenhamos que mexer em nenhum desses códigos, ainda é útil saber o que está acontecendo nos bastidores da configuração automática do Spring Boot.
Além disso, a configuração dos beans Transaction Manager e Entity Manager é a mesma do aplicativo Spring padrão.
7. Conclusão
Este artigo foi uma visão geral prática de como configurar seu projeto Spring Data JPA para usar vários bancos de dados.
Ofull implementation deste artigo pode ser encontrado emthe GitHub project - este é um projeto baseado em Maven, portanto, deve ser fácil de importar e executar como está.