Spring JPA - Mehrere Datenbanken

Spring JPA - Mehrere Datenbanken

1. Überblick

In diesem Tutorial implementieren wir eine einfache Spring-Konfiguration fürSpring Data JPA system with multiple databases.

Weitere Lektüre:

Spring Data JPA - Abgeleitete Löschmethoden

Erfahren Sie, wie Sie die Spring Data-Methoden deleteBy und removeBy definieren

Read more

Programmgesteuertes Konfigurieren einer DataSource in Spring Boot

Erfahren Sie, wie Sie eine Spring Boot DataSource programmgesteuert konfigurieren und dabei den automatischen DataSource-Konfigurationsalgorithmus von Spring Boot umgehen.

Read more

2. Die Entitäten

Zunächst erstellen wir zwei einfache Entitäten, die jeweils in einer separaten Datenbank leben.

Hier ist die erste Entität „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;
}

Und die zweite Entität - „Product“:

package com.example.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,the two entities are also placed in independent packages - dies ist wichtig, wenn wir uns der Konfiguration zuwenden.

Weitere Lektüre:

Spring Data JPA - Abgeleitete Löschmethoden

Erfahren Sie, wie Sie die Spring Data-Methoden deleteBy und removeBy definieren

Read more

Programmgesteuertes Konfigurieren einer DataSource in Spring Boot

Erfahren Sie, wie Sie eine Spring Boot DataSource programmgesteuert konfigurieren und dabei den automatischen DataSource-Konfigurationsalgorithmus von Spring Boot umgehen.

Read more

3. Die JPA-Repositorys

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

package com.example.multipledb.dao.user;

public interface UserRepository
  extends JpaRepository { }

UndProductRepository:

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

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

  • Datenquelle

  • EntityManagerFactory (userEntityManager)

  • TransactionManager (userTransactionManager)

Beginnen wir mit der Benutzerkonfiguration:

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

Beachten Sie, wie wiruserTransactionManager alsPrimary TransactionManager verwenden, indem Sie die Bean-Definition mit@Primary versehen. Dies ist immer dann hilfreich, wenn wir den Transaktionsmanager implizit oder explizit einfügen möchten, ohne anzugeben, welcher Name genannt wird.

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

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

Spring Boot kann die oben beschriebene Konfiguration vereinfachen.

StandardmäßigSpring 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]

Wir wollen jetzt weiterhin den gleichen Weg wieconfigure the second DataSource, but with a different property namespace: verwenden

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

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

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

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

Der interessante Teil istannotating the data source bean creation method with*@ConfigurationProperties*. Wir müssen nur das entsprechende Konfigurationspräfix. angeben. In dieser Methode verwenden wirDataSourceBuilder, und Spring Boot kümmert sich automatisch darum der Rest.

Aber wie werden die konfigurierten Eigenschaften tatsächlich in die Konfiguration vonDataSourceeingefügt?

Beim Aufrufen derbuild()-Methode fürDataSourceBuilder wird die privatebind()-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 tatsächlicheDataSource-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 Spring Boot-Autokonfiguration passiert.

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

7. Fazit

Dieser Artikel bietet einen praktischen Überblick über die Konfiguration Ihres Spring Data JPA-Projekts für die Verwendung mehrerer Datenbanken.

Diefull implementation dieses Artikels befinden sich inthe GitHub project - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.