跳到主要内容
版本:4.1.1

提前优化

Doubao 中英对照 Ahead of Time Optimizations

本章介绍 Spring Data 的提前(AOT)优化,这些优化建立在 Spring 的提前优化 之上。

最佳实践​

为领域类型添加注解​

在应用程序启动期间,Spring 会扫描 classpath 以查找领域类,从而对实体进行早期处理。通过使用特定于 Spring Data 存储的 @Table、@Document 或 @Entity 注解为您的领域类型添加注解,您可以辅助初始实体扫描,并确保这些类型被注册到 ManagedTypes 以供运行时提示使用。在原生镜像环境中无法进行 classpath 扫描,因此 Spring 必须使用 ManagedTypes 来确定初始实体集合。

提前代码生成​

提前代码生成不仅限于与 GraalVM 原生镜像配合使用,在常规部署中也能带来好处,并有助于优化 JVM 上的启动性能。

备注

借助 AOT 优化,某些决策(例如数据库方言)将在构建期被固定下来,并按原样包含到应用程序配置中。

如果启用了提前编译,Spring Data 可以(取决于实际使用的模块)在构建的 AOT 阶段贡献若干组件。

  • 为生成的类型/属性访问器生成字节码

  • 为已定义的仓库接口生成源代码

  • JSON 格式的仓库元数据

以上各项默认均已启用。不过,您可以通过以下选项对配置进行微调。

spring.aot.data.accessors.enabled布尔标志,用于控制是否为生成的类型/属性访问器贡献字节码
spring.aot.data.accessors.include以逗号分隔的 FQCN 列表,仅为这些类生成类型/属性访问器字节码。Ant 风格的包含模式,用于匹配包名(例如 example.springdata.**)或类型名。如果某个类型同时被包含和排除规则匹配,则包含规则优先,该类型视为被包含。
spring.aot.data.accessors.exclude以逗号分隔的 FQCN 列表,不为这些类生成类型/属性访问器字节码。Ant 风格的排除模式,用于匹配包名(例如 example.springdata.**)或类型名。如果某个类型同时被包含和排除规则匹配,则包含规则优先,该类型视为被包含。
spring.aot.repositories.enabled布尔标志,用于控制是否为仓库接口贡献源代码
spring.aot.[module-name].repositories.enabled布尔标志,用于控制是否为某个模块(例如 cassandra、jdbc、jpa、mongodb)的仓库接口贡献源代码
spring.aot.repositories.metadata.enabled布尔标志,用于控制是否贡献包含查询方法及实际查询字符串的 JSON 仓库元数据。需要先启用 spring.aot.repositories.enabled。

提前仓库​

备注

提前仓库仅适用于某些模块的命令式(非响应式)仓库接口。用于判定合格查询方法的标准在各实现模块之间有所不同。

提前仓库通过预生成合格查询方法的实现,对 AOT 处理进行了扩展。对于查询方法调用时底层执行的查询,开发者是无从知晓的。提前仓库基于构建期已知的派生查询、注解查询和命名查询来贡献查询方法实现。这项优化将查询方法的处理从运行时移到构建期,由于无需在每次应用启动时通过反射分析查询方法,从而可显著提升性能。

生成的提前仓库片段遵循 <Repository FQCN>Impl__AotRepository 的命名规则,并与仓库接口放在同一个包中。

注意

请将提前仓库类视为内部优化。请勿在您的代码中直接使用它们,因为生成方式与实现细节在未来版本中可能发生变化。

仓库元数据​

AOT 处理会内省查询方法并收集有关仓库查询的元数据。Spring Data 将这些元数据存储在 JSON 文件中,这些文件与源仓库同名并位于同一包中。仓库 JSON 元数据包含有关查询和片段的详细信息。下面以如下仓库为例进行说明:

{
"name": "example.springdata.UserRepository",
"module": "JDBC",
"type": "IMPERATIVE",
"methods": [
{
"name": "findBy",
"signature": "public abstract java.util.List<example.springdata.User> example.springdata.UserRepository.findBy()",
"query": {
"query": "SELECT * FROM User"
}
},
{
"name": "findByLastnameStartingWith",
"signature": "public abstract org.springframework.data.domain.Page<example.springdata.User> example.springdata.UserRepository.findByLastnameStartingWith(java.lang.String,org.springframework.data.domain.Pageable)",
"query": {
"query": "SELECT * FROM User u WHERE lastname LIKE :lastname",
"count-query": "SELECT COUNT(*) FROM User WHERE lastname LIKE :lastname"
}
},
{
"name": "findByEmailAddress",
"signature": "public abstract example.springdata.User example.springdata.UserRepository.findByEmailAddress(java.lang.String)",
"query": {
"query": "select * from User where emailAddress = ?1"
}
},
备注

是否创建 JSON 元数据可通过 spring.aot.repositories.metadata.enabled 标志控制。

原生镜像运行时提示​

与常规 JVM 运行时相比,以原生镜像方式运行应用程序需要额外的信息。Spring Data 会在 AOT 处理期间为原生镜像使用贡献运行时提示。这些提示尤其针对:

  • 审计

  • ManagedTypes,用于捕获 classpath 扫描的结果

  • 仓库

    • 针对实体、返回类型和 Spring Data 注解的反射提示

    • 仓库片段

    • Querydsl Q 类

    • Kotlin 协程支持

  • Web 支持(针对 PagedModel 的 Jackson 提示)

JPA 提前仓库​

提前仓库会筛选出适合 AOT 处理的方法。这些方法通常是指所有不由实现片段支撑的查询方法。

备注

AOT 处理会避免访问数据库。因此,它会初始化一个内存中的 Hibernate 实例来收集元数据。Hibernate 配置所用的类型由我们的 AOT 元数据收集器确定。如果可用,我们优先使用 PersistenceManagedTypes Bean,否则回退到 PersistenceUnitInfo 或我们自己发现的类型。如果我们的类型扫描不足以满足您的环境,您可以通过配置 spring.aot.jpa.repositories.use-entitymanager=true 属性来启用直接使用 EntityManagerFactory。

支持的特性

  • 派生查询方法、@Query/@NativeQuery 以及命名查询方法

  • 使用 @Procedure 注解的存储过程查询方法

  • 返回 void 或 int 的 @Modifying 方法

  • @QueryHints 支持

  • 分页、Slice、Stream 和 Optional 返回类型

  • 排序查询重写

  • 接口投影与 DTO 投影

  • 值表达式(这些需要少量反射信息。请注意,使用值表达式需要表达式解析和上下文信息才能求值表达式)

限制

  • AOT 处理需要 Hibernate。

  • QueryRewriter 必须是一个无参类。尚不支持 QueryRewriter Bean。

  • 尚不支持接受 ScrollPosition 的方法(例如 Keyset 分页)。

  • 尚不支持自定义集合返回类型(例如 io.vavr.collection、org.eclipse.collections)。

被排除的方法

  • CrudRepository、Querydsl、按示例查询以及其他基础接口方法,因为它们的实现由基类相应片段提供

  • 实现过于复杂的方法

    • 接受 ScrollPosition 的方法(例如 Keyset 分页)

    • 动态投影