Spring JPA - 多数据库

2023/05/18

1. 概述

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

延伸阅读

Spring Data JPA-派生的删除方法

了解如何定义Spring Data deleteBy和removeBy方法

阅读更多

在Spring Boot中以编程方式配置数据源

了解如何以编程方式配置Spring Boot数据源,从而避开Spring Boot的自动数据源配置算法。

阅读更多

2. 实体

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

这是第一个用户实体:

package cn.tuyucheng.taketoday.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;
}

这是第二个产品实体:

package cn.tuyucheng.taketoday.multipledb.model.product;

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

    @Id
    private int id;

    private String name;

    private double price;
}

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

3. JPA Repository

接下来,让我们看一下我们的两个JPA Repository,UserRepository:

package cn.tuyucheng.taketoday.multipledb.dao.user;

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

和ProductRepository:

package cn.tuyucheng.taketoday.multipledb.dao.product;

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

再次注意,这两个Repository也是放在不同的包中。

4. 使用Java配置JPA

现在我们将进入实际的Spring配置。我们需要首先设置两个配置类-一个用于User,另一个用于Product

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

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

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

@Configuration
@PropertySource({"classpath:persistence-multiple-db.properties"})
@EnableJpaRepositories(
      basePackages = "cn.tuyucheng.taketoday.multipledb.dao.user",
      entityManagerFactoryRef = "userEntityManager",
      transactionManagerRef = "userTransactionManager"
)
@Profile("!tc")
public class PersistenceUserConfiguration {
    @Autowired
    private Environment env;

    public PersistenceUserConfiguration() {
        super();
    }

    @Primary
    @Bean
    public LocalContainerEntityManagerFactoryBean userEntityManager() {
        System.out.println("loading config");
        final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(userDataSource());
        em.setPackagesToScan("cn.tuyucheng.taketoday.multipledb.model.user");

        final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        final 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() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(Preconditions.checkNotNull(env.getProperty("jdbc.driverClassName")));
        dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("user.jdbc.url")));
        dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("jdbc.user")));
        dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("jdbc.pass")));

        return dataSource;
    }

    @Primary
    @Bean
    public PlatformTransactionManager userTransactionManager() {
        final 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 = "cn.tuyucheng.taketoday.multipledb.dao.product",
      entityManagerFactoryRef = "productEntityManager",
      transactionManagerRef = "productTransactionManager"
)
@Profile("!tc")
public class PersistenceProductConfiguration {
    @Autowired
    private Environment env;

    public PersistenceProductConfiguration() {
        super();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean productEntityManager() {
        final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(productDataSource());
        em.setPackagesToScan("cn.tuyucheng.taketoday.multipledb.model.product");

        final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        final HashMap<String, Object> properties = new HashMap<String, Object>();
        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() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(Preconditions.checkNotNull(env.getProperty("jdbc.driverClassName")));
        dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("product.jdbc.url")));
        dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("jdbc.user")));
        dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("jdbc.pass")));

        return dataSource;
    }

    @Bean
    public PlatformTransactionManager productTransactionManager() {
        final JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(productEntityManager().getObject());
        return transactionManager;
    }
}

5. 简单测试

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

为此,我们将为每个实体创建一个实例并确保它已创建:

@ExtendWith(SpringExtension.class)
@SpringBootTest
@EnableTransactionManagement
class JpaMultipleDBIntegrationTest {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ProductRepository productRepository;

    @Test
    @Transactional("userTransactionManager")
    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")
    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 ignored) {
        }

        assertNull(userRepository.findOne(user2.getId()));
    }

    @Test
    @Transactional("productTransactionManager")
    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 = "cn.tuyucheng.taketoday.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 = "cn.tuyucheng.taketoday.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
}

现在我们已经根据Spring Boot自动配置约定在persistence-multiple-db-boot.properties中定义了数据源属性。

有趣的部分是使用@ConfigurationProperties标注数据源bean创建方法,我们只需要指定相应的配置前缀即可。在此方法中,我们使用了DataSourceBuilder,Spring Boot将自动处理其余部分。

但是如何将配置的属性注入到DataSource配置中呢?

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

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

这个私有方法执行了很多自动配置魔法,将解析的配置绑定到实际的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));
}

尽管我们自己不必接触任何这些代码,但了解Spring Boot自动配置的幕后情况仍然很有用。

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

7. 总结

本文是关于如何配置我们的Spring Data JPA项目以使用多个数据库的实用概述。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章