数据访问

Spring Boot 提供了多个用于数据源操作的 starter。 本节解答与数据源相关的问题。

配置自定义 DataSource

要配置你自己的 DataSource,只需在配置类中定义一个该类型的 @Bean。 Spring Boot 会在任何需要的地方(包括数据库初始化)复用你的 DataSource。 如果需要将部分设置外部化,可以将 DataSource 绑定到环境(参见 第三方配置)。

以下示例展示了如何在 bean 中定义数据源:

  • Java

  • Kotlin

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {

	@Bean
	@ConfigurationProperties("app.datasource")
	public SomeDataSource dataSource() {
		return new SomeDataSource();
	}

}
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

	@Bean
	@ConfigurationProperties("app.datasource")
	fun dataSource(): SomeDataSource {
		return SomeDataSource()
	}

}

以下示例展示了如何通过属性设置数据源:

  • Properties

  • YAML

app.datasource.url=jdbc:h2:mem:mydb
app.datasource.username=sa
app.datasource.pool-size=30
app:
  datasource:
    url: "jdbc:h2:mem:mydb"
    username: "sa"
    pool-size: 30

假设 SomeDataSource 拥有 URL、用户名和连接池大小等常规 JavaBean 属性,这些设置会在 DataSource 被其他组件使用前自动绑定。

Spring Boot 还提供了一个名为 DataSourceBuilder 的实用构建器类,可用于创建标准数据源(如果在 classpath 上)。 该构建器会根据 classpath 上可用内容自动检测使用哪种数据源,并根据 JDBC URL 自动检测驱动。

以下示例展示了如何使用 DataSourceBuilder 创建数据源:

  • Java

  • Kotlin

import javax.sql.DataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {

	@Bean
	@ConfigurationProperties("app.datasource")
	public DataSource dataSource() {
		return DataSourceBuilder.create().build();
	}

}
import javax.sql.DataSource

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

	@Bean
	@ConfigurationProperties("app.datasource")
	fun dataSource(): DataSource {
		return DataSourceBuilder.create().build()
	}

}

要运行带有该 DataSource 的应用,只需提供连接信息。 也可以提供特定于连接池的设置。 具体细节请参考运行时所用实现。

以下示例展示了如何通过属性定义 JDBC 数据源:

  • Properties

  • YAML

app.datasource.url=jdbc:mysql://localhost/test
app.datasource.username=dbuser
app.datasource.password=dbpass
app.datasource.pool-size=30
app:
  datasource:
    url: "jdbc:mysql://localhost/test"
    username: "dbuser"
    password: "dbpass"
    pool-size: 30

但由于方法返回类型为 DataSource,实际连接池类型被隐藏,因此不会为你的自定义 DataSource 生成配置属性元数据,IDE 也无法自动补全。 为解决此问题,可使用构建器的 type(Class) 方法指定要构建的 DataSource 类型,并更新方法返回类型。 例如,以下展示了如何用 DataSourceBuilder 创建 HikariDataSource

  • Java

  • Kotlin

import com.zaxxer.hikari.HikariDataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {

	@Bean
	@ConfigurationProperties("app.datasource")
	public HikariDataSource dataSource() {
		return DataSourceBuilder.create().type(HikariDataSource.class).build();
	}

}
import com.zaxxer.hikari.HikariDataSource
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

	@Bean
	@ConfigurationProperties("app.datasource")
	fun dataSource(): HikariDataSource {
		return DataSourceBuilder.create().type(HikariDataSource::class.java).build()
	}

}

但这种基础配置并不适用于 Hikari,因为 Hikari 没有 url 属性,而是 jdbc-url,因此你必须将配置改写如下:

  • Properties

  • YAML

app.datasource.jdbc-url=jdbc:mysql://localhost/test
app.datasource.username=dbuser
app.datasource.password=dbpass
app.datasource.pool-size=30
app:
  datasource:
    jdbc-url: "jdbc:mysql://localhost/test"
    username: "dbuser"
    password: "dbpass"
    pool-size: 30

为解决此问题,可使用 DataSourceProperties,它会帮你处理 urljdbc-url 的转换。 你可以通过其 initializeDataSourceBuilder() 方法,从任意 DataSourceProperties 对象的状态初始化 DataSourceBuilder。 你可以注入 Spring Boot 自动创建的 DataSourceProperties,但那样会让你的配置分散在 spring.datasource.*app.datasource.*。 为避免此问题,可自定义一个带有自定义配置前缀的 DataSourceProperties,如下例所示:

  • Java

  • Kotlin

import com.zaxxer.hikari.HikariDataSource;

import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration(proxyBeanMethods = false)
public class MyDataSourceConfiguration {

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

	@Bean
	@ConfigurationProperties("app.datasource.configuration")
	public HikariDataSource dataSource(DataSourceProperties properties) {
		return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
	}

}
import com.zaxxer.hikari.HikariDataSource
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Primary

@Configuration(proxyBeanMethods = false)
class MyDataSourceConfiguration {

	@Bean
	@Primary
	@ConfigurationProperties("app.datasource")
	fun dataSourceProperties(): DataSourceProperties {
		return DataSourceProperties()
	}

	@Bean
	@ConfigurationProperties("app.datasource.configuration")
	fun dataSource(properties: DataSourceProperties): HikariDataSource {
		return properties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build()
	}

}

这种配置等价于 Spring Boot 默认行为,只是连接池类型在代码中指定,相关设置以 app.datasource.configuration.* 属性暴露。 DataSourceProperties 负责 urljdbc-url 的转换,因此你可以这样配置:

  • Properties

  • YAML

app.datasource.url=jdbc:mysql://localhost/test
app.datasource.username=dbuser
app.datasource.password=dbpass
app.datasource.configuration.maximum-pool-size=30
app:
  datasource:
    url: "jdbc:mysql://localhost/test"
    username: "dbuser"
    password: "dbpass"
    configuration:
      maximum-pool-size: 30

注意,由于自定义配置在代码中指定了 Hikari,app.datasource.type 不再生效。

支持的连接池 所述,DataSourceBuilder 支持多种连接池。 如需使用非 Hikari 连接池,将其加入 classpath,使用 type(Class) 方法指定连接池类,并更新 @Bean 方法返回类型。 这样还能为你选择的连接池生成配置属性元数据。

Spring Boot 会将 Hikari 专属设置暴露为 spring.datasource.hikari。 本例采用更通用的 configuration 子命名空间,因为示例不支持多种数据源实现。

更多细节参见 配置 DataSourceDataSourceAutoConfiguration 类。

配置两个 DataSource

要定义额外的 DataSource,可采用与前述类似的方法。 关键区别在于 DataSource@Bean 必须声明 defaultCandidate=false,以防止自动配置的 DataSource 退让。

Spring Framework 参考文档对此特性有更详细说明。

如需让额外的 DataSource 可被注入,还需用 @Qualifier 注解,如下例所示:

  • Java

  • Kotlin

import com.zaxxer.hikari.HikariDataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyAdditionalDataSourceConfiguration {

	@Qualifier("second")
	@Bean(defaultCandidate = false)
	@ConfigurationProperties("app.datasource")
	public HikariDataSource secondDataSource() {
		return DataSourceBuilder.create().type(HikariDataSource.class).build();
	}

}
import com.zaxxer.hikari.HikariDataSource

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyAdditionalDataSourceConfiguration {

	@Qualifier("second")
	@Bean(defaultCandidate = false)
	@ConfigurationProperties("app.datasource")
	fun secondDataSource(): HikariDataSource {
		return DataSourceBuilder.create().type(HikariDataSource::class.java).build()
	}

}

要使用该额外的 DataSource,在注入点同样加上 @Qualifier

自动配置的数据源和额外数据源可按如下方式配置:

  • Properties

  • YAML

spring.datasource.url=jdbc:mysql://localhost/first
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.configuration.maximum-pool-size=30
app.datasource.url=jdbc:mysql://localhost/second
app.datasource.username=dbuser
app.datasource.password=dbpass
app.datasource.max-total=30
spring:
  datasource:
    url: "jdbc:mysql://localhost/first"
    username: "dbuser"
    password: "dbpass"
    configuration:
      maximum-pool-size: 30
app:
  datasource:
    url: "jdbc:mysql://localhost/second"
    username: "dbuser"
    password: "dbpass"
    max-total: 30

更高级、特定实现的数据源配置可通过 spring.datasource.configuration.* 属性实现。 你也可以对额外的数据源采用同样思路,如下例所示:

  • Java

  • Kotlin

import com.zaxxer.hikari.HikariDataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyCompleteAdditionalDataSourceConfiguration {

	@Qualifier("second")
	@Bean(defaultCandidate = false)
	@ConfigurationProperties("app.datasource")
	public DataSourceProperties secondDataSourceProperties() {
		return new DataSourceProperties();
	}

	@Qualifier("second")
	@Bean(defaultCandidate = false)
	@ConfigurationProperties("app.datasource.configuration")
	public HikariDataSource secondDataSource(
			@Qualifier("secondDataSourceProperties") DataSourceProperties secondDataSourceProperties) {
		return secondDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
	}

}
import com.zaxxer.hikari.HikariDataSource

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyCompleteAdditionalDataSourceConfiguration {

	@Qualifier("second")
	@Bean(defaultCandidate = false)
	@ConfigurationProperties("app.datasource")
	fun secondDataSourceProperties(): DataSourceProperties {
		return DataSourceProperties()
	}

	@Qualifier("second")
	@Bean(defaultCandidate = false)
	@ConfigurationProperties("app.datasource.configuration")
	fun secondDataSource(secondDataSourceProperties: DataSourceProperties): HikariDataSource {
		return secondDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource::class.java).build()
	}

}

上述示例用与 Spring Boot 自动配置相同的逻辑配置了额外数据源。 注意,app.datasource.configuration.* 属性根据所选实现提供高级设置。

配置单个自定义 DataSource,可通过 DataSourceBuildertype(Class) 方法自定义一个或两个 DataSource bean 的类型。 支持类型详见 支持的连接池

使用 Spring Data Repositories

Spring Data 可为多种风格的 Repository 接口自动创建实现。 只要这些 Repository 实现包含在 自动配置包(通常是主应用类所在包或其子包,且带有 @SpringBootApplication@EnableAutoConfiguration 注解)中,Spring Boot 会自动处理。

对于大多数应用,只需将合适的 Spring Data 依赖加入 classpath。 JPA 可用 spring-boot-starter-data-jpa,MongoDB 可用 spring-boot-starter-data-mongodb,其它技术也有相应 starter。 开始时,创建一些用于处理 @Entity 对象的 repository 接口即可。

Spring Boot 通过扫描 自动配置包确定 Repository 实现的位置。 如需更细致控制,可用 Spring Data 的 @Enable…Repositories 注解。

更多 Spring Data 相关内容,参见 Spring Data 项目主页

将 @Entity 定义与 Spring 配置分离

Spring Boot 通过扫描 自动配置包确定 @Entity 定义的位置。 如需更细致控制,可用 @EntityScan 注解,如下例所示:

  • Java

  • Kotlin

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EntityScan(basePackageClasses = City.class)
public class MyApplication {

	// ...

}
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.domain.EntityScan
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration
@EntityScan(basePackageClasses = [City::class])
class MyApplication {

	// ...

}

过滤扫描到的 @Entity 定义

可通过 ManagedClassNameFilter bean 过滤 @Entity 定义。 这在测试时只需考虑部分实体时很有用。 如下例,仅包含 com.example.app.customer 包下的实体:

  • Java

  • Kotlin

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.persistenceunit.ManagedClassNameFilter;

@Configuration(proxyBeanMethods = false)
public class MyEntityScanConfiguration {

	@Bean
	public ManagedClassNameFilter entityScanFilter() {
		return (className) -> className.startsWith("com.example.app.customer.");
	}

}
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.orm.jpa.persistenceunit.ManagedClassNameFilter

@Configuration(proxyBeanMethods = false)
class MyEntityScanConfiguration {

	@Bean
	fun entityScanFilter() : ManagedClassNameFilter {
		return ManagedClassNameFilter { className ->
			className.startsWith("com.example.app.customer.")
		}
	}
}

配置 JPA 属性

Spring Data JPA 已提供部分与厂商无关的配置选项(如 SQL 日志),Spring Boot 还将这些选项及部分 Hibernate 选项暴露为外部配置属性。 部分属性会根据上下文自动检测,无需手动设置。

spring.jpa.hibernate.ddl-auto 是一个特殊配置项,因为它会根据运行时条件采用不同的默认值。 如果使用的是嵌入式数据库,且没有像 Liquibase 或 Flyway 这样的 schema 管理器处理 DataSource,则默认值为 create-drop。 在所有其他情况下,默认值为 none

JPA 提供者会自动检测要使用的方言(dialect)。 如果你希望自行指定方言,请设置 spring.jpa.database-platform 属性。

最常用的配置选项如下例所示:

  • Properties

  • YAML

spring.jpa.hibernate.naming.physical-strategy=com.example.MyPhysicalNamingStrategy
spring.jpa.show-sql=true
spring:
  jpa:
    hibernate:
      naming:
        physical-strategy: "com.example.MyPhysicalNamingStrategy"
    show-sql: true

此外,所有 spring.jpa.properties.* 下的属性在创建本地 EntityManagerFactory 时会作为常规 JPA 属性(前缀会被去除)传递。

你需要确保在 spring.jpa.properties.* 下定义的名称与 JPA 提供者所期望的完全一致。 Spring Boot 不会对这些条目做任何宽松绑定。

例如,如果你想配置 Hibernate 的批量大小,必须使用 spring.jpa.properties.hibernate.jdbc.batch_size。 如果使用其他形式,如 batchSizebatch-size,Hibernate 将不会应用该设置。

如果你需要对 Hibernate 属性进行高级自定义,可以注册一个 HibernatePropertiesCustomizer bean,该 bean 会在创建 EntityManagerFactory 之前被调用。 这会优先生效于自动配置应用的任何内容。

配置 Hibernate 命名策略

Hibernate 使用 两种不同的命名策略,将对象模型中的名称映射到对应的数据库名称。 可以通过设置 spring.jpa.hibernate.naming.physical-strategyspring.jpa.hibernate.naming.implicit-strategy 属性,分别配置物理和隐式策略实现类的全限定类名。 或者,如果应用上下文中存在 ImplicitNamingStrategyPhysicalNamingStrategy bean,Hibernate 会自动使用它们。

默认情况下,Spring Boot 使用 CamelCaseToUnderscoresNamingStrategy 配置物理命名策略。 采用该策略时,所有点(.)会被下划线替换,驼峰命名也会被下划线分隔。 此外,默认所有表名都会转为小写。 例如,TelephoneNumber 实体会映射为 telephone_number 表。 如果你的 schema 需要混合大小写标识符,可以像下面这样自定义 CamelCaseToUnderscoresNamingStrategy bean:

  • Java

  • Kotlin

import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyHibernateConfiguration {

	@Bean
	public CamelCaseToUnderscoresNamingStrategy caseSensitivePhysicalNamingStrategy() {
		return new CamelCaseToUnderscoresNamingStrategy() {

			@Override
			protected boolean isCaseInsensitive(JdbcEnvironment jdbcEnvironment) {
				return false;
			}

		};
	}

}
import org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyHibernateConfiguration {

	@Bean
	fun caseSensitivePhysicalNamingStrategy(): CamelCaseToUnderscoresNamingStrategy {
		return object : CamelCaseToUnderscoresNamingStrategy() {
			override fun isCaseInsensitive(jdbcEnvironment: JdbcEnvironment): Boolean {
				return false
			}
		}
	}

}

如果你更倾向于使用 Hibernate 的默认策略,只需设置如下属性:

  • Properties

  • YAML

spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring:
  jpa:
    hibernate:
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

或者,你也可以配置如下 bean:

  • Java

  • Kotlin

import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
class MyHibernateConfiguration {

	@Bean
	PhysicalNamingStrategyStandardImpl caseSensitivePhysicalNamingStrategy() {
		return new PhysicalNamingStrategyStandardImpl();
	}

}
import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
internal class MyHibernateConfiguration {

	@Bean
	fun caseSensitivePhysicalNamingStrategy(): PhysicalNamingStrategyStandardImpl {
		return PhysicalNamingStrategyStandardImpl()
	}

}

配置 Hibernate 二级缓存

Hibernate 二级缓存 可针对多种缓存提供者进行配置。 通常建议直接提供应用上下文中已有的缓存实现,而不是让 Hibernate 再次查找缓存提供者。

若要结合 JCache 使用,首先确保 org.hibernate.orm:hibernate-jcache 已在 classpath 中。 然后,添加一个 HibernatePropertiesCustomizer bean,如下例所示:

  • Java

  • Kotlin

import org.hibernate.cache.jcache.ConfigSettings;

import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class MyHibernateSecondLevelCacheConfiguration {

	@Bean
	public HibernatePropertiesCustomizer hibernateSecondLevelCacheCustomizer(JCacheCacheManager cacheManager) {
		return (properties) -> properties.put(ConfigSettings.CACHE_MANAGER, cacheManager.getCacheManager());
	}

}
import org.hibernate.cache.jcache.ConfigSettings
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer
import org.springframework.cache.jcache.JCacheCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyHibernateSecondLevelCacheConfiguration {

	@Bean
	fun hibernateSecondLevelCacheCustomizer(cacheManager: JCacheCacheManager): HibernatePropertiesCustomizer {
		return HibernatePropertiesCustomizer { properties ->
			properties[ConfigSettings.CACHE_MANAGER] = cacheManager.cacheManager
		}
	}

}

该自定义器会让 Hibernate 使用与应用一致的 CacheManager。 当然,也可以使用不同的 CacheManager 实例。 详情请参见 Hibernate 用户指南

在 Hibernate 组件中使用依赖注入

默认情况下,Spring Boot 会注册一个基于 BeanFactoryBeanContainer 实现,这样转换器和实体监听器就可以使用常规依赖注入。

你可以通过注册一个 HibernatePropertiesCustomizer,移除或修改 hibernate.resource.beans.container 属性,从而禁用或调整该行为。

使用自定义 EntityManagerFactory

如果你需要完全控制 EntityManagerFactory 的配置,只需添加一个名为 ‘entityManagerFactory’ 的 @Bean。 只要存在该类型的 bean,Spring Boot 的自动配置就会关闭自身的 entity manager。

如果你自行创建了 LocalContainerEntityManagerFactoryBean,则自动配置期间应用的任何自定义都会失效。 请务必使用自动配置的 EntityManagerFactoryBuilder,以保留 JPA 及厂商属性。 如果你依赖 spring.jpa.* 属性配置命名策略或 DDL 模式,这一点尤为重要。

使用多个 EntityManagerFactory

如果你需要针对多个数据源使用 JPA,通常每个数据源都需要一个 EntityManagerFactory。 Spring ORM 提供的 LocalContainerEntityManagerFactoryBean 允许你为每个数据源配置 EntityManagerFactory。 你还可以复用 JpaProperties,为第二个 EntityManagerFactory 绑定设置。 基于 配置第二个 DataSource 的示例,可以如下定义第二个 EntityManagerFactory

  • Java

  • Kotlin

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

@Configuration(proxyBeanMethods = false)
public class MyAdditionalEntityManagerFactoryConfiguration {

	@Qualifier("second")
	@Bean(defaultCandidate = false)
	@ConfigurationProperties("app.jpa")
	public JpaProperties secondJpaProperties() {
		return new JpaProperties();
	}

	@Qualifier("second")
	@Bean(defaultCandidate = false)
	public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory(@Qualifier("second") DataSource dataSource,
			@Qualifier("second") JpaProperties jpaProperties) {
		EntityManagerFactoryBuilder builder = createEntityManagerFactoryBuilder(jpaProperties);
		return builder.dataSource(dataSource).packages(Order.class).persistenceUnit("second").build();
	}

	private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) {
		JpaVendorAdapter jpaVendorAdapter = createJpaVendorAdapter(jpaProperties);
		Function<DataSource, Map<String, ?>> jpaPropertiesFactory = (dataSource) -> createJpaProperties(dataSource,
				jpaProperties.getProperties());
		return new EntityManagerFactoryBuilder(jpaVendorAdapter, jpaPropertiesFactory, null);
	}

	private JpaVendorAdapter createJpaVendorAdapter(JpaProperties jpaProperties) {
		// ... map JPA properties as needed
		return new HibernateJpaVendorAdapter();
	}

	private Map<String, ?> createJpaProperties(DataSource dataSource, Map<String, ?> existingProperties) {
		Map<String, ?> jpaProperties = new LinkedHashMap<>(existingProperties);
		// ... map JPA properties that require the DataSource (e.g. DDL flags)
		return jpaProperties;
	}

}
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.orm.jpa.JpaVendorAdapter
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
import javax.sql.DataSource

@Configuration(proxyBeanMethods = false)
class MyAdditionalEntityManagerFactoryConfiguration {

	@Qualifier("second")
	@Bean(defaultCandidate = false)
	@ConfigurationProperties("app.jpa")
	fun secondJpaProperties(): JpaProperties {
		return JpaProperties()
	}

	@Qualifier("second")
	@Bean(defaultCandidate = false)
	fun firstEntityManagerFactory(
		@Qualifier("second") dataSource: DataSource,
		@Qualifier("second") jpaProperties: JpaProperties
	): LocalContainerEntityManagerFactoryBean {
		val builder = createEntityManagerFactoryBuilder(jpaProperties)
		return builder.dataSource(dataSource).packages(Order::class.java).persistenceUnit("second").build()
	}

	private fun createEntityManagerFactoryBuilder(jpaProperties: JpaProperties): EntityManagerFactoryBuilder {
		val jpaVendorAdapter = createJpaVendorAdapter(jpaProperties)
		val jpaPropertiesFactory = { dataSource: DataSource ->
				createJpaProperties(dataSource, jpaProperties.properties) }
		return EntityManagerFactoryBuilder(jpaVendorAdapter, jpaPropertiesFactory, null)
	}

	private fun createJpaVendorAdapter(jpaProperties: JpaProperties): JpaVendorAdapter {
		// ... map JPA properties as needed
		return HibernateJpaVendorAdapter()
	}

	private fun createJpaProperties(dataSource: DataSource, existingProperties: Map<String, *>): Map<String, *> {
		val jpaProperties: Map<String, *> = LinkedHashMap(existingProperties)
		// ... map JPA properties that require the DataSource (e.g. DDL flags)
		return jpaProperties
	}

}

上述示例通过 @Qualifier("second") 注解,使用名为 second 的 DataSource bean 创建 EntityManagerFactory。 它会扫描与 Order 同包下的实体。 也可以通过 app.jpa 命名空间映射额外的 JPA 属性。 使用 @Bean(defaultCandidate=false) 可定义 secondJpaPropertiessecondEntityManagerFactory bean,而不会影响同类型的自动配置 bean。

Spring Framework 参考文档对此特性有更详细说明。

如需为更多数据源提供 JPA 支持,可采用类似配置。 此外,还需为每个 EntityManagerFactory 配置一个 JpaTransactionManager。 或者,也可以使用同时管理多个数据源的 JTA 事务管理器。

如果你使用 Spring Data,需要相应配置 @EnableJpaRepositories,如下例所示:

  • Java

  • Kotlin

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = Order.class, entityManagerFactoryRef = "entityManagerFactory")
public class OrderConfiguration {

}
import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaRepositories

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = [Order::class], entityManagerFactoryRef = "firstEntityManagerFactory")
class OrderConfiguration
  • Java

  • Kotlin

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = Customer.class, entityManagerFactoryRef = "secondEntityManagerFactory")
public class CustomerConfiguration {

}
import org.springframework.context.annotation.Configuration
import org.springframework.data.jpa.repository.config.EnableJpaRepositories

@Configuration(proxyBeanMethods = false)
@EnableJpaRepositories(basePackageClasses = [Customer::class], entityManagerFactoryRef = "secondEntityManagerFactory")
class CustomerConfiguration

使用传统 persistence.xml 文件

Spring Boot 默认不会查找或使用 META-INF/persistence.xml。 如果你更倾向于使用传统的 persistence.xml,需要自行定义一个类型为 LocalEntityManagerFactoryBean(ID 为 ‘entityManagerFactory’)的 @Bean,并在其中设置持久化单元名称。

默认设置请参见 JpaBaseConfiguration

同时使用 Spring Data JPA 与 Mongo 仓库

Spring Data JPA 和 Spring Data Mongo 都可以自动为你创建 Repository 实现。 如果它们同时存在于 classpath 上,可能需要额外配置以告知 Spring Boot 需要创建哪些仓库。 最直接的方式是使用标准的 Spring Data @EnableJpaRepositories@EnableMongoRepositories 注解,并指定 Repository 接口的位置。

你还可以通过配置 spring.data.*.repositories.enabledspring.data.*.repositories.type 等标志,在外部配置中切换自动配置的仓库开关。 例如,如果你只想关闭 Mongo 仓库但仍使用自动配置的 MongoTemplate,这种方式非常有用。

对于其他自动配置的 Spring Data 仓库类型(如 Elasticsearch、Redis 等),同样适用。 只需相应更改注解和配置项名称即可。

自定义 Spring Data 的 Web 支持

Spring Data 提供了 Web 支持,简化了在 Web 应用中使用 Spring Data 仓库的流程。 Spring Boot 提供了 spring.data.web 命名空间下的属性用于自定义相关配置。 注意,如果你使用 Spring Data REST,则应使用 spring.data.rest 命名空间下的属性。

将 Spring Data 仓库暴露为 REST 端点

Spring Data REST 可以将 Repository 实现自动暴露为 REST 端点,前提是应用已启用 Spring MVC。

Spring Boot 提供了一组有用的属性(位于 spring.data.rest 命名空间),用于自定义 RepositoryRestConfiguration。 如需进一步自定义,可使用 RepositoryRestConfigurer bean。

如果你没有为自定义的 RepositoryRestConfigurer 指定顺序(order),它会在 Spring Boot 内部使用的配置器之后运行。 如需指定顺序,请确保其值大于 0。

配置 JPA 依赖的组件

如果你需要配置一个被 JPA 使用的组件,必须确保该组件在 JPA 之前初始化。 如果该组件是自动配置的,Spring Boot 会自动处理依赖顺序。 例如,当 Flyway 被自动配置时,Hibernate 会被配置为依赖 Flyway,从而保证 Flyway 能在 Hibernate 使用数据库前完成初始化。

如果你自行配置组件,可以通过继承 EntityManagerFactoryDependsOnPostProcessor,方便地设置所需依赖关系。 例如,如果你将 Hibernate Search 与 Elasticsearch 结合使用,所有 EntityManagerFactory bean 都必须依赖 elasticsearchClient bean,如下例所示:

  • Java

  • Kotlin

import jakarta.persistence.EntityManagerFactory;

import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.stereotype.Component;

/**
 * {@link EntityManagerFactoryDependsOnPostProcessor} that ensures that
 * {@link EntityManagerFactory} beans depend on the {@code elasticsearchClient} bean.
 */
@Component
public class ElasticsearchEntityManagerFactoryDependsOnPostProcessor
		extends EntityManagerFactoryDependsOnPostProcessor {

	public ElasticsearchEntityManagerFactoryDependsOnPostProcessor() {
		super("elasticsearchClient");
	}

}
import org.springframework.boot.autoconfigure.orm.jpa.EntityManagerFactoryDependsOnPostProcessor
import org.springframework.stereotype.Component

@Component
class ElasticsearchEntityManagerFactoryDependsOnPostProcessor :
	EntityManagerFactoryDependsOnPostProcessor("elasticsearchClient")

配置 jOOQ 支持多个数据源

如果你需要让 jOOQ 支持多个数据源,应为每个数据源分别创建 DSLContext。 更多细节请参见 JooqAutoConfiguration

特别地,ExceptionTranslatorExecuteListenerSpringTransactionProvider 可复用,实现类似单一 DataSource 自动配置下的功能。