Spring JPA - Database multipli

1. Panoramica

In questo tutorial, implementeremo una semplice configurazione Spring per un sistema JPA Spring Data con più database .

2. Le entità

Per prima cosa, creiamo due semplici entità, ciascuna che vive in un database separato.

Ecco la prima entità " Utente ":

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; }

E la seconda entità - " Prodotto ":

package com.baeldung.multipledb.model.product; @Entity @Table(schema = "products") public class Product { @Id private int id; private String name; private double price; }

Come puoi vedere, le due entità sono anche collocate in pacchetti indipendenti: questo sarà importante quando ci spostiamo nella configurazione.

3. I repository JPA

Successivamente, diamo un'occhiata ai nostri due repository JPA - UserRepository :

package com.baeldung.multipledb.dao.user; public interface UserRepository extends JpaRepository { }

E ProductRepository :

package com.baeldung.multipledb.dao.product; public interface ProductRepository extends JpaRepository { }

Nota, ancora una volta, come abbiamo creato questi due repository in pacchetti diversi.

4. Configurare JPA con Java

Avanti: passiamo alla configurazione Spring effettiva. Inizieremo impostando due classi di configurazione: una per l' utente e l'altra per il prodotto .

In ciascuna di queste classi di configurazione, dovremo definire le seguenti interfacce per l' utente :

  • Fonte di dati
  • EntityManagerFactory ( userEntityManager )
  • TransactionManager ( userTransactionManager )

Cominciamo guardando la configurazione dell'utente:

@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; } }

Notare come stiamo usando userTransactionManager come nostro TransactionManager primario , annotando la definizione del bean con @Primary . È utile ogni volta che inseriremo implicitamente o esplicitamente il gestore delle transazioni senza specificare quale per nome.

Successivamente, discutiamo PersistenceProductConfiguration , dove definiamo bean simili:

@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. Test semplice

Infine, testiamo le nostre configurazioni.

Proveremo un semplice test creando un'istanza di ciascuna entità e assicurandoci che sia creata, come nell'esempio seguente:

@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. Più database in Spring Boot

Spring Boot può semplificare la configurazione di cui sopra.

Per impostazione predefinita, Spring Boot creerà un'istanza del DataSource predefinito con le proprietà di configurazione precedute da spring.datasource. * :

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

Ora vogliamo continuare a utilizzare lo stesso modo per configurare il secondo DataSource , ma con uno spazio dei nomi di proprietà diverso:

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

Poiché vogliamo che l'autoconfigurazione di Spring Boot raccolga quelle diverse proprietà (e crei un'istanza di due diverse DataSource ), definiremo due classi di configurazione simili a quelle nelle sezioni precedenti:

@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 } 

Abbiamo definito le proprietà dell'origine dati all'interno di persistence-multiple-db-boot.properties in base alla convenzione di configurazione automatica di avvio.

La parte interessante è annotare il metodo di creazione del bean di origine dati con @ConfigurationProperties . Abbiamo solo bisogno di specificare il prefisso di configurazione corrispondente . All'interno di questo metodo, stiamo utilizzando un DataSourceBuilder e Spring Boot si occuperà automaticamente del resto.

Ma come vengono inserite le proprietà configurate nella configurazione DataSource ?

Quando si chiama il metodo build () su DataSourceBuilder , chiamerà il suo metodo bind () privato :

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

Questo metodo privato esegue gran parte della magia dell'autoconfigurazione, associando la configurazione risolta all'istanza di DataSource effettiva :

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)); }

Anche se non dobbiamo toccare nessuno di questo codice da soli, è comunque utile sapere cosa sta succedendo sotto il cofano dell'autoconfigurazione di Spring Boot.

Inoltre, la configurazione dei bean Transaction Manager ed Entity Manager è la stessa dell'applicazione Spring standard.

7. Conclusione

Questo articolo rappresentava una panoramica pratica di come configurare il progetto JPA Spring Data per utilizzare più database.

L' implementazione completa di questo articolo può essere trovata nel progetto GitHub: questo è un progetto basato su Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.