@月黑风高食肉虎 噗噗虎的技术博客

Spring Data JPA 多数据源配置总结

Spring boot中Jpa的自动配置

Spring Boot 中,jpa 自动配置在JpaBaseConfiguration中能看到。 JpaBaseConfiguration是一个抽象类,只有HibernateJpaAutoConfiguration一个子类(?)

JpaBaseConfiguration提供JPA默认的基础配置,HibernateJpaAutoConfiguration则提供Vendor专有的配置。 其实JPA的Vendor只有Hibernate使用最广泛。

JpaBaseConfiguration主要提供4个Bean:

  • PlatformTransactionManager类型的transactionManager
  • JpaVendorAdapter
  • EntityManagerFactoryBuilder
  • LocalContainerEntityManagerFactoryBean类型的entityManagerFactory

这4个bean的主要职责分别为:

  • 为JPA提供事务支持,注意这里需要使用JpaTransactionManager
  • EntityManagerFactoryBuilder提供Vender相关的特殊信息
  • 用来构建EntityManagerFactory的Builder
  • 使用EntityManagerFactoryBuilder构建的entityManagerFactory

而在JpaBaseConfiguration的子类HibernateJpaAutoConfiguration中,则提供了Hibernate相关的Vender特殊配置。 另外,其中部分Hiberante专有Vender相关特殊配置来源于JpaProperties.getHibernateProperties(datasource)方法。 特别是hiberante.id.new_generator_mappings相关的配置,这个配置决定了Hibernate是否启用新ID Generator。

如果没有特别配置,并且当前hibernate的版本是5的话,这个设置会被默认设置为false。 在MySQL中,这决定了strategy=AUTO@GeneratedValue是使用Column的auto increment 还是走由hibernate生成的sequence表(hibernate_sequence)。

JpaBaseConfiguration启用了一个配置属性Bean(ConfigurationProperties):

  • JpaProperties 它的前缀为spring.jpa,用来参与jpa相关的配置。另外它还提供了Hibernate作为JPA的Vender相关的配置。

Spring boot中Datasource的自动配置

自动配置参考DataSourceAutoConfiguration类,这个类的主要目的是为了根据动态条件导入一下几个类:

  • EmbeddedDataSourceConfiguration,此类负责配置h2嵌入式数据库
  • DataSourceConfiguration.Tomcat/Hikari/Dbcp/Dbcp2/Generic,此类负责配置数据库链接缓存池。

当自动配置条件符合h2的时候,EmbeddedDataSourceConfiguration会被导入并启用,可以在该类中看到, 真正的datasource是通过EmbeddedDatabaseBuilder加载DataSourceProperties类来构建的。

当自动配置条件符合其他,也就是Tomcat/Hikari/Dbcp/Dbcp2/Generic的情况下, DataSource是通过DataSourceProperties类中的initializeDataSourceBuilder方法返回的DataSourceBuilder来构建的。

DataSourceProperties类的基本职责是为了读取application.yml文件中spring.datasource下的各项配置项的。 而该类中的initializeDataSourceBuilder方法则是将DataSourceProperties读取的数据库配置 放入一个新DataSourceBuilder实例并返回。

DataSourceConfiguration类中,不同类型的数据库链接池通过对DataSourceProperties传入不同的type 来指定DataSourceBuilder构建不同类型的数据源。这个type可以通过spring.datasource.type来指定。

这个spring.datasource.type可以指定的值有:

  • org.apache.tomcat.jdbc.pool.DataSource
  • com.zaxxer.hikari.HikariDataSource
  • org.apache.commons.dbcp.BasicDataSource
  • org.apache.commons.dbcp2.BasicDataSource

可以看到这些类都是常用的db pool。并且,如果在当前classpath上存在这些类中的某一个,但是 又没有在application.yml中指定spring.datasource.type,这些相应的配置也会生效。 譬如如果当前系统是tomcat容器,在classpath上存在org.apache.tomcat.jdbc.pool.DataSource, 但并没有在application.yml中指定spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource, 则Tomcat的数据库缓存池也会起效。

如果没有指定任何spring.datasource.type,并且在classpath上也不存在上述那些DataSource类, 则不会针对DataSourceBuilder指定任何type。一般来说会直接new一个在application.yml中 指定的driverClass的对象(一般来说是DataSource接口的实现)出来并返回。

另外需要提一下的是,如果你指定了type,或者知道系统会明确使用哪个db pool的话, 本来指定在spring.datasource下的配置可以指定到相应的type类型下,譬如tomcat的话可以指定到 spring.datasource.tomcat下,hikari的话是spring.datasource.hikari下。

针对DataSourceBuilder,我们还需要特别说明的是,当DataSourceBuilder build datasource的时候, 如果当前没有指定任何type,它还会再次尝试通过Class.forName的方式去加载上述4个db pool, 如果失败了,则会报No supported DataSource Type found的错。

另外,如果你没有指定driverClassName,这个类还会通过猜测database的url来获取driver class name。

自行配置数据源和相应的JPA

知道了Spring boot是如何自动配置JPA和数据源的,我们就能手动配置了。

首先需要配置数据源,数据源最简单的配置方式是,首先使用@ConfigurationProperties将相应的数据库配置 注入DataSourceProperties对象。然后使用该对象的initializeDataSourceBuilder方法初始化一个 DataSourceBuilder然后build即可。譬如

@Bean
@ConfigurationProperties("spring.datasource")
public DataSourceProperties dataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
public DataSource dataSource() {
    return dataSourceProperties().initializeDataSourceBuilder().build();
}

这样我们就能根据spring.datasource下的配置注入数据源了。

然后JPA需要注入两个Bean,一个是LocalContainerEntityManagerFactoryBean,此FactoryBean 主要用来为JPA提供EntityManager。另外一个是PlatformTransactionManager,此transactionManager 主要为EntityManager提供事务管理的支持。

其中,LocalContainerEntityManagerFactoryBean可以使用auto config提供的EntityManagerFactoryBuilder来构建。 然后,为了让Spring Data JPA知道我们需要使用Repository并且扫描指定的目录,可以使用@EnableJpaRepository注解 来指定相应的package。另外,我们还可以使用JpaProperties来获取spring.jpa下的vender相关配置。

一个示例配置如下:

@Configuration
@EnableConfigurationProperties(JpaRepoerties.class)
@EnableJpaRepository(basePackageClasses = {RepositoryScanMarker.class}) // 指定Spring Data JPA的Repository所在包
public class JpaRepositoryConfiguration {

    @Autowired
    private JpaProperties jpaProperties;

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, DataSource datasource) {
        return builder.dataSource(datasource)  // 指定数据源
            .packages(EntityScanMarker.class) // 指定Entity所在包
            .properties(jpaProperties.getHibernateProperties(dataSource)) // 获取并注入hibernate vender相关配置
            .persistenceUnit("default") // 指定persistence unit
            .build();
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory); // 构建事务管理器
    }
}

如果要配置多个数据源和多个entityManager,只需要注入多个LocalContainerEntityManagerFactoryBean 和多个transactionManager即可。但需要注意以下事项:

  • 为一个LocalContainerEntityManagerFactoryBeantransactionManager的bean增加@Primary, 这样如果后面有自动配置的情况,系统能知道默认使用哪个EntityManagerFactoryBean和哪个transactionManager
  • @EnableJpaRepository会自动使用默认名字为entityManagerFactoryLocalContainerEntityManagerFactoryBean 和默认名字为transactionManagerTransactionManager。如果要指定多个EntityManager的情况, 需要在@EnableJpaRepository上使用entityManagerFactoryReftransactionManagerRef另外指定不同的 LocalContainerEntityManagerFactoryBeanTransactionManager.
  • 另外,TransactionManager必须是JpaTransactionManager!必须是JpaTransactionManager! 必须是JpaTransactionManager!入过的坑必须要说3遍。