Spring JPA - Mehrere Datenbanken

1. Übersicht

In diesem Tutorial implementieren wir eine einfache Spring-Konfiguration für ein Spring Data JPA-System mit mehreren Datenbanken .

2. Die Entitäten

Lassen Sie uns zunächst zwei einfache Entitäten erstellen, die jeweils in einer separaten Datenbank leben.

Hier ist die erste " Benutzer " -Entität:

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

Und die zweite Einheit - „ Produkt “:

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

Wie Sie sehen können, werden die beiden Entitäten auch in unabhängigen Paketen platziert. Dies ist wichtig, wenn wir mit der Konfiguration fortfahren.

3. Die JPA-Repositorys

Als nächstes werfen wir einen Blick auf unsere beiden JPA-Repositorys - UserRepository :

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

Und ProductRepository :

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

Beachten Sie erneut, wie wir diese beiden Repositorys in verschiedenen Paketen erstellt haben.

4. Konfigurieren Sie JPA mit Java

Weiter - kommen wir zur eigentlichen Spring-Konfiguration. Zunächst richten wir zwei Konfigurationsklassen ein - eine für den Benutzer und eine für das Produkt .

In jeder dieser Konfigurationsklassen müssen die folgenden Schnittstellen für Benutzer definiert werden :

  • Datenquelle
  • EntityManagerFactory ( userEntityManager )
  • TransactionManager ( userTransactionManager )

Beginnen wir mit der Benutzerkonfiguration:

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

Beachten Sie, wie wir den userTransactionManager als primären TransactionManager verwenden, indem Sie die Bean-Definition mit @Primary kommentieren . Dies ist immer dann hilfreich, wenn wir den Transaktionsmanager implizit oder explizit einfügen möchten, ohne den Namen anzugeben.

Als nächstes diskutieren wir PersistenceProductConfiguration - wo wir ähnliche Beans definieren:

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

Zum Schluss testen wir unsere Konfigurationen.

Wir werden einen einfachen Test versuchen, indem wir eine Instanz jeder Entität erstellen und sicherstellen, dass sie erstellt wird - wie im folgenden Beispiel:

@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. Mehrere Datenbanken im Spring Boot

Spring Boot kann die obige Konfiguration vereinfachen.

Standardmäßig instanziiert Spring Boot seine Standard- DataSource mit den Konfigurationseigenschaften, denen spring.datasource vorangestellt ist. * :

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

Wir möchten nun weiterhin dieselbe Methode zum Konfigurieren der zweiten DataSource verwenden , jedoch mit einem anderen Eigenschafts-Namespace:

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

Da die automatische Konfiguration von Spring Boot diese unterschiedlichen Eigenschaften übernehmen (und zwei verschiedene DataSources instanziieren ) soll, definieren wir zwei Konfigurationsklassen, die denen in den vorherigen Abschnitten ähneln:

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

Wir haben die Datenquelleneigenschaften in persistence-multiple-db-boot.properties gemäß der Konvention für die automatische Boot-Konfiguration definiert.

Der interessante Teil ist das Kommentieren der Datenquellen-Bean-Erstellungsmethode mit @ConfigurationProperties . Wir müssen nur das entsprechende Konfigurationspräfix angeben . Innerhalb dieser Methode verwenden wir einen DataSourceBuilder, und Spring Boot kümmert sich automatisch um den Rest.

Aber wie werden die konfigurierten Eigenschaften in die DataSource- Konfiguration eingefügt ?

Beim Aufrufen der build () -Methode im DataSourceBuilder wird die private bind () -Methode aufgerufen :

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

Diese private Methode führt einen Großteil der Magie der Autokonfiguration aus und bindet die aufgelöste Konfiguration an die eigentliche DataSource- Instanz:

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

Obwohl wir keinen dieser Codes selbst berühren müssen, ist es dennoch nützlich zu wissen, was unter der Haube der automatischen Konfiguration von Spring Boot passiert.

Außerdem entspricht die Konfiguration der Transaction Manager- und Entity Manager-Beans der Standard-Spring-Anwendung.

7. Fazit

Dieser Artikel gab einen praktischen Überblick darüber, wie Sie Ihr Spring Data JPA-Projekt für die Verwendung mehrerer Datenbanken konfigurieren.

Die vollständige Implementierung dieses Artikels finden Sie im GitHub-Projekt - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.