Contents

配置 Spring Data JPA 使用多个数据库

1.概述

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

2. 实体

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

这是第一个*User * 实体:

package com.blogdemo.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;
}

这是第二个实体Product

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

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

3. JPA 存储库

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

package com.blogdemo.multipledb.dao.user;
public interface UserRepository
  extends JpaRepository<User, Integer> { }

ProductRepository

package com.blogdemo.multipledb.dao.product;
public interface ProductRepository
  extends JpaRepository<Product, Integer> { }

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

4. 用Java配置JPA

现在我们将了解实际的 Spring 配置。我们将首先设置两个配置类——一个用于User,另一个用于Product 在每个配置类中,我们需要为User定义以下接口:

  • DataSource
  • *EntityManagerFactory *(userEntityManager
  • *TransactionManager *(userTransactionManager

让我们从查看用户配置开始:

@Configuration
@PropertySource({ "classpath:persistence-multiple-db.properties" })
@EnableJpaRepositories(
    basePackages = "com.blogdemo.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.blogdemo.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;
    }
}

请注意我们如何通过使用 @Primary 注解 bean 定义来使用userTransactionManager作为我们的主要的 TransactionManager。每当我们要隐式或显式地注入事务管理器而不指定哪个名称时,这都会很有帮助。 接下来,让我们讨论PersistenceProductConfiguration,我们在其中定义了类似的 bean:

@Configuration
@PropertySource({ "classpath:persistence-multiple-db.properties" })
@EnableJpaRepositories(
    basePackages = "com.blogdemo.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.blogdemo.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. 简单测试

最后,让我们测试一下我们的配置。 为此,我们将创建每个实体的实例并确保它已创建:

@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]](/cdn_cgi/l/email_protection)");
        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]](/cdn_cgi/l/email_protection)");
        user1.setAge(20);
        user1 = userRepository.save(user1);
        assertNotNull(userRepository.findOne(user1.getId()));
        User user2 = new User();
        user2.setName("Tom");
        user2.setEmail("[[email protected]](/cdn_cgi/l/email_protection)");
        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. Spring Boot 中的多个数据库

Spring Boot 可以简化上面的配置。 默认情况下,Spring Boot 将使用前缀为spring.datasource.*的配置属性实例化其默认DataSource

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

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

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

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

@Configuration
@PropertySource({"classpath:persistence-multiple-db-boot.properties"})
@EnableJpaRepositories(
  basePackages = "com.blogdemo.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.blogdemo.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
}

现在我们已经根据 Boot 自动配置约定在persistence-multiple-db-boot.properties中定义了数据源属性。 有趣的部分是使用** @ConfigurationProperties注解数据源 bean 创建方法。我们只需要指定相应的配置前缀在这个方法中,我们使用了一个DataSourceBuilder,Spring Boot 会自动处理剩下的事情。

但是配置的属性如何被注入到DataSource配置中呢? 在DataSourceBuilder上调用*build()方法时,它会调用其私有的bind()*方法: