Spring JPA - Plusieurs bases de données

Spring JPA - Plusieurs bases de données

1. Vue d'ensemble

Dans ce tutoriel, nous allons implémenter une configuration Spring simple pour unSpring Data JPA system with multiple databases.

Lectures complémentaires:

Spring Data JPA - Méthodes de suppression dérivées

Apprenez à définir les méthodes Spring Data deleteBy et removeBy

Read more

Configuration d'une source de données par programme lors du démarrage printanier

Apprenez à configurer une source de données Spring Boot par programmation, en suivant ainsi l'algorithme de configuration automatique DataSource de Spring Boot.

Read more

2. Les Entités

Commençons par créer deux entités simples, chacune vivant dans une base de données distincte.

Voici la première entité «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;
}

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

package com.example.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,the two entities are also placed in independent packages - ce sera important lorsque nous passerons à la configuration.

Lectures complémentaires:

Spring Data JPA - Méthodes de suppression dérivées

Apprenez à définir les méthodes Spring Data deleteBy et removeBy

Read more

Configuration d'une source de données par programme lors du démarrage printanier

Apprenez à configurer une source de données Spring Boot par programmation, en suivant ainsi l'algorithme de configuration automatique DataSource de Spring Boot.

Read more

3. Les référentiels JPA

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

package com.example.multipledb.dao.user;

public interface UserRepository
  extends JpaRepository { }

EtProductRepository:

package com.example.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 Spring réelle. We’ll start by setting up 2 configuration classes – one for the User and the other for the Product.

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

  • La source de données

  • EntityManagerFactory (userEntityManager)

  • TransactionManager (userTransactionManager)

Commençons par examiner la configuration utilisateur:

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

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

Ensuite, parlons deProductConfig - où nous définissons des beans similaires:

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

Enfin, testons nos configurations.

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

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

Spring Boot peut simplifier la configuration ci-dessus.

Par défaut,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]

Nous voulons maintenant continuer à utiliser la même manière pourconfigure the second DataSource, but with a different property namespace:

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

Étant donné que nous voulons que la configuration automatique de Spring Boot récupère ces différentes propriétés (et instancie en fait deuxDataSources différents), nous allons définir 2 classes de configuration similaires à celles des sections précédentes:

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

Nous avons défini les propriétés de la source de données danspersistence-multiple-db-boot.properties selon la convention de configuration automatique de démarrage.

La partie intéressante estannotating the data source bean creation method with*@ConfigurationProperties*. Nous avons juste besoin de spécifier le préfixe de configuration correspondant. Dans cette méthode, nous utilisons unDataSourceBuilder, et Spring Boot se chargera automatiquement de le reste.

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

Lors de l'appel de la méthodebuild() sur leDataSourceBuilder, il appellera sa méthode privéebind():

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 réelle deDataSource:

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 identique à celle de l'application Spring standard.

7. Conclusion

Cet article était une présentation pratique de la configuration de votre projet Spring Data JPA pour utiliser plusieurs bases de données.

Lesfull implementation de cet article se trouvent dansthe GitHub project - il s'agit d'un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.