Spring JPA - Bases de données multiples

1. Vue d'ensemble

Dans ce didacticiel, nous allons implémenter une configuration Spring simple pour un système Spring Data JPA avec plusieurs bases de données .

2. Les entités

Tout d'abord, créons deux entités simples, chacune vivant dans une base de données distincte.

Voici la première entité « Utilisateur »:

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

Et la deuxième entité - « Produit »:

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

Comme vous pouvez le voir, les deux entités sont également placées dans des packages indépendants - ce sera important lorsque nous passerons à la configuration.

3. Les référentiels JPA

Ensuite - jetons un coup d'œil à nos deux référentiels JPA - UserRepository :

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

Et ProductRepository :

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

Notez encore une fois comment nous avons créé ces deux référentiels dans des packages différents.

4. Configurer JPA avec Java

Ensuite, passons à la configuration réelle de Spring. Nous allons commencer par configurer deux classes de configuration - une pour l' utilisateur et l'autre pour le produit .

Dans chacune de ces classes de configuration, nous devrons définir les interfaces suivantes pour User :

  • La source de données
  • EntityManagerFactory ( userEntityManager )
  • TransactionManager ( userTransactionManager )

Commençons par regarder la configuration de l'utilisateur:

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

Remarquez comment nous utilisons userTransactionManager en tant que TransactionManager principal - en annotant la définition du bean avec @Primary . C'est utile chaque fois que nous allons injecter implicitement ou explicitement le gestionnaire de transactions sans spécifier lequel par son nom.

Ensuite, discutons de PersistenceProductConfiguration - où nous définissons des beans similaires:

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

Enfin - testons nos configurations.

Nous allons essayer un test simple en créant une instance de chaque entité et nous assurer qu'elle est créée - comme dans l'exemple suivant:

@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. Bases de données multiples dans Spring Boot

Spring Boot peut simplifier la configuration ci-dessus.

Par défaut, Spring Boot instanciera sa DataSource par défaut avec les propriétés de configuration préfixées par spring.datasource. * :

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

Nous voulons maintenant continuer à utiliser la même façon de configurer la deuxième DataSource , mais avec un espace de noms de propriété différent:

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

Étant donné que nous voulons que l'autoconfiguration Spring Boot récupère ces différentes propriétés (et instancie deux DataSources différentes ), nous définirons deux classes de configuration similaires à celles des sections précédentes:

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

Nous avons défini les propriétés de la source de données dans persistence-multiple-db-boot.properties conformément à la convention de configuration automatique de démarrage.

La partie intéressante est d' annoter la méthode de création du bean source de données avec @ConfigurationProperties . Nous avons juste besoin de spécifier le préfixe de configuration correspondant . Dans cette méthode, nous utilisons un DataSourceBuilder, et Spring Boot s'occupera automatiquement du reste.

Mais comment les propriétés configurées sont-elles injectées dans la configuration DataSource ?

Lors de l'appel de la méthode build () sur le DataSourceBuilder , il appellera sa méthode bind () privée :

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

Cette méthode privée effectue une grande partie de la magie de la configuration automatique, liant la configuration résolue à l' instance DataSource réelle :

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

Bien que nous n'ayons pas à toucher à ce code nous-mêmes, il est toujours utile de savoir ce qui se passe sous le capot de la configuration automatique de Spring Boot.

En outre, la configuration des beans Transaction Manager et Entity Manager est la même que celle de l'application Spring standard.

7. Conclusion

Cet article était un aperçu pratique de la configuration de votre projet Spring Data JPA pour utiliser plusieurs bases de données.

L' implémentation complète de cet article se trouve dans le projet GitHub - il s'agit d'un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.