Spring JPA – Multiple Databases – Spring JPA – 多个数据库

最后修改: 2015年 1月 31日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll implement a simple Spring configuration for a Spring Data JPA system with multiple databases.

在本教程中,我们将为一个具有多个数据库的Spring Data JPA系统实现一个简单的Spring配置。

2. The Entities

2.实体

First, let’s create two simple entities, with each living in a separate database.

首先,让我们创建两个简单的实体,每个实体生活在一个单独的数据库中。

Here is the first User entity:

这里是第一个User实体。

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

And here’s the second entity, Product:

而这里是第二个实体,产品

package com.baeldung.multipledb.model.product;

@Entity
@Table(schema = "products")
public class Product {

    @Id
    private int id;

    private String name;

    private double price;
}

We can see that the two entities are also placed in independent packages. This will be important as we move into the configuration.

我们可以看到,这两个实体也被放置在独立的包中。这在我们进入配置时将非常重要。

3. The JPA Repositories

3.JPA存储库

Next, let’s take a look at our two JPA repositories, UserRepository:

接下来,让我们看看我们的两个JPA存储库,UserRepository

package com.baeldung.multipledb.dao.user;

public interface UserRepository
  extends JpaRepository<User, Integer> { }

and ProductRepository:

ProductRepository

package com.baeldung.multipledb.dao.product;

public interface ProductRepository
  extends JpaRepository<Product, Integer> { }

Note again how we created these two repositories in different packages.

再次注意我们是如何在不同的软件包中创建这两个存储库的。

4. Configure JPA With Java

4.用Java配置JPA

Now we’ll get to the actual Spring configuration. We’ll first set up two configuration classes — one for the User and the other for the Product.

现在我们将开始实际的Spring配置。我们将首先设置两个配置类–一个用于User,另一个用于Product

In each configuration class, we’ll need to define the following interfaces for User:

在每个配置类中,我们需要为User定义以下接口。

  • DataSource
  • EntityManagerFactory (userEntityManager)
  • TransactionManager (userTransactionManager)

Let’s start by looking at the User configuration:

让我们先看一下用户配置。

@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<String, Object> 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;
    }
}

Notice how we use the userTransactionManager as our Primary TransactionManager by annotating the bean definition with @Primary. That’s helpful whenever we’re going to implicitly or explicitly inject the transaction manager without specifying which one by name.

请注意我们是如何使用userTransactionManager作为我们的Primary TransactionManager的,通过在bean定义中注解@Primary。当我们要隐式或显式地注入事务管理器时,这很有帮助,不需要通过名称来指定哪一个。

Next, let’s discuss PersistenceProductConfiguration, where we define similar beans:

接下来,让我们讨论一下 PersistenceProductConfiguration,我们在这里定义类似的bean。

@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<String, Object> 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. Simple Test

5.简单测试

Finally, let’s test our configurations.

最后,让我们测试一下我们的配置。

To do that, we will create an instance of each entity and make sure it is created:

要做到这一点,我们将为每个实体创建一个实例,并确保它被创建。

@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("john@test.com");
        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("john@test.com");
        user1.setAge(20);
        user1 = userRepository.save(user1);
        assertNotNull(userRepository.findOne(user1.getId()));

        User user2 = new User();
        user2.setName("Tom");
        user2.setEmail("john@test.com");
        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. Multiple Databases in Spring Boot

6.Spring Boot中的多个数据库

Spring Boot can simplify the configuration above.

Spring Boot可以简化上述配置。

By default, Spring Boot will instantiate its default DataSource with the configuration properties prefixed by spring.datasource.*:

默认情况下,Spring Boot将实例化其默认的DataSource,配置属性前缀为spring.datasource.*

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

We now want to keep using the same way to configure the second DataSource, but with a different property namespace:

我们现在要继续使用同样的方式来配置第二个DataSource,但要使用不同的属性命名空间

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

Because we want the Spring Boot autoconfiguration to pick up those different properties (and instantiate two different DataSources), we’ll define two configuration classes similar to the previous sections:

因为我们希望Spring Boot的自动配置能够获取这些不同的属性(并实例化两个不同的DataSources),所以我们将定义两个配置类,与前几节类似。

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

Now we have defined the data source properties inside persistence-multiple-db-boot.properties according to the Boot autoconfiguration convention.

现在我们已经根据Boot自动配置的惯例,在persistence-multiple-db-boot.properties内定义了数据源属性。

The interesting part is annotating the data source bean creation method with @ConfigurationProperties. We just need to specify the corresponding config prefix. Inside this method, we’re using a DataSourceBuilder, and Spring Boot will automatically take care of the rest.

有趣的部分是@ConfigurationProperties创建数据源Bean的方法。我们只需要指定相应的配置前缀.在这个方法里面,我们使用的是DataSourceBuilder,Spring Boot会自动处理其余的事情。

But how do the configured properties get injected into the DataSource configuration?

但是,配置的属性如何被注入到DataSource配置中?

When calling the build() method on the DataSourceBuilder, it’ll call its private bind() method:

在调用DataSourceBuilder上的build()方法时,它将调用其私有的bind()方法。

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

This private method performs much of the autoconfiguration magic, binding the resolved configuration to the actual DataSource instance:

这个私有方法执行了大部分的自动配置魔法,将已解决的配置绑定到实际的DataSource实例。

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

Although we don’t have to touch any of this code ourselves, it’s still useful to know what’s happening under the hood of the Spring Boot autoconfiguration.

虽然我们不需要自己去碰这些代码,但了解Spring Boot自动配置的内部情况还是很有用的。

Besides this, the Transaction Manager and Entity Manager beans configuration is the same as the standard Spring application.

除此之外,事务管理器和实体管理器的Bean配置与标准Spring应用程序相同。

7. Conclusion

7.结论

This article was a practical overview of how to configure our Spring Data JPA project to use multiple databases.

这篇文章是对如何配置我们的Spring Data JPA项目以使用多个数据库的一个实用概述。

The full implementation can be found in the GitHub project. This is a Maven-based project, so it should be easy to import and run as it is.

完整的实现可以在GitHub项目中找到。这是一个基于Maven的项目,所以应该很容易导入并按原样运行。