跳到主要内容
版本:5.1.1

提前优化(AOT)

Doubao 中英对照 Ahead of Time Optimizations

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

最佳实践​

为领域类型添加注解​

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

运行时提示​

与常规 JVM 运行时相比,将应用作为原生镜像运行需要额外的信息。Spring Data 会在 AOT 处理期间贡献运行时提示,供原生镜像使用。这些提示特别针对:

  • 审计

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

  • 仓库

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

    • 仓库片段

    • Querydsl Q 类

    • Kotlin 协程支持

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

提前仓库​

AOT 仓库是 AOT 处理的一种扩展,它预先生成符合条件的查询方法实现。对于开发者而言,查询方法在调用时所执行的底层查询是不透明的。AOT 仓库基于构建时已知的派生查询、注解查询和命名查询来贡献查询方法实现。这种优化将查询方法处理从运行时移到构建时,从而带来显著的性能提升,因为查询方法无需在每次应用启动时通过反射进行分析。

生成的 AOT 仓库片段遵循 <Repository FQCN>Impl__Aot 的命名方案,并放置在与仓库接口相同的包中。您可以找到生成的仓库查询方法以字符串形式表示的所有查询。

备注

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

使用 AOT 仓库运行​

AOT 是将 Spring 应用转换为原生可执行文件的必经步骤,因此在这种模式下运行时会自动启用。当启用 AOT 时(无论是用于原生编译,还是通过设置 spring.aot.enabled=true),AOT 仓库默认会自动启用。

您可以完全禁用 AOT 仓库生成,也可以仅禁用 Cassandra AOT 仓库:

  • 设置 spring.aot.repositories.enabled=false 属性,可为所有 Spring Data 模块禁用生成的仓库。

  • 设置 spring.aot.cassandra.repositories.enabled=false 属性,仅禁用 Cassandra AOT 仓库。

AOT 仓库会对实际的仓库 bean 注册贡献配置变更,以注册生成的仓库片段。

备注

当包含 AOT 优化时,某些在构建时做出的决策会被硬编码到应用配置中。例如,在构建时启用的 profile 在运行时也会自动启用。此外,实现某个仓库的 Spring Data 模块也是固定的。更改实现需要重新进行 AOT 处理。

符合条件的方法​

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

支持的特性

  • 派生查询方法、@Query 注解方法和命名查询方法。

  • Window、Slice、Stream 和 Optional 返回类型

  • 排序查询重写

  • 接口投影和 DTO 投影

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

限制

  • 尚不支持向量搜索

排除的方法

  • CrudRepository 及其他基础接口方法,因为它们的实现由基类(即相应片段)提供

  • 向量搜索方法

仓库元数据​

AOT 处理会内省查询方法,并收集有关仓库查询的元数据。Spring Data Cassandra 将这些元数据存储在 JSON 文件中,这些文件以仓库接口命名,并位于其旁边(即在同一包中)。仓库 JSON 元数据包含有关查询和片段的详细信息。以下面这个仓库为例:

interface UserRepository extends CrudRepository<User, Integer> {

List<User> findUserNoArgumentsBy(); 1

Slice<User> findSliceOfUsersByLastnameStartingWith(String lastname, Pageable page); 2

@Query("select * from User where emailAddress = :emailAddress")
User findAnnotatedQueryByEmailAddress(String emailAddress); 3

User findByEmailAddress(String emailAddress); 4
}
  • 不带参数的派生查询。

  • 使用分页的派生查询。

  • 注解查询。

  • 命名查询。

{
"name": "com.acme.UserRepository",
"module": "Cassandra",
"type": "IMPERATIVE",
"methods": [
{
"name": "findUserNoArgumentsBy",
"signature": "public abstract java.util.List<com.acme.User> com.acme.UserRepository.findUserNoArgumentsBy()",
"query": {
"query": "select * from user"
}
},
{
"name": "findSliceOfUsersByLastnameStartingWith",
"signature": "public abstract org.springframework.data.domain.Slice<com.acme.User> com.acme.UserRepository.findSliceOfUsersByLastnameStartingWith(java.lang.String,org.springframework.data.domain.Pageable)",
"query": {
"query": "select * from user where lastname like ?"
}
},
{
"name": "findAnnotatedQueryByEmailAddress",
"signature": "public abstract com.acme.User com.acme.UserRepository.findAnnotatedQueryByEmailAddress(java.lang.String)",
"query": {
"query": "select * from user where emailaddress = ?"
}
},
{
"name": "findByEmailAddress",
"signature": "public abstract com.acme.User com.acme.UserRepository.findByEmailAddress(java.lang.String)",
"query": {
"name": "User.findByEmailAddress",
"query": "select * from user where emailaddress = ?"
}
},
{
"name": "count",
"signature": "public abstract long org.springframework.data.repository.CrudRepository.count()",
"fragment": {
"fragment": "org.springframework.data.cassandra.repository.support.SimpleCassandraRepository"
}
}
]
}

查询可包含以下字段:

  • query:如果该方法是查询方法,则为查询描述符。

    • query 用于获取查询方法结果的查询。

    • name:如果查询是命名查询,则为命名查询的名称。

  • fragment:如果方法调用被委托给某个存储(仓库基类、诸如 Querydsl 之类的功能片段)或用户片段,则为目标片段。如果没有进一步的接口,片段仅用 fragment 描述;如果存在接口(例如 Querydsl 或用户声明的片段接口),则以 interface 和 fragment 元组描述。

备注

规范化查询形式

对查询的静态分析只能表示有限的运行时查询行为。查询以其规范化(预解析并重写)形式表示:

  • 值表达式被替换为绑定标记。

  • 查询元数据不反映绑定值的处理。StartingWith/EndingWith 查询会在实际绑定值前/后添加通配符 %。

  • 运行时排序信息无法并入查询字符串本身,因为该细节在构建时未知。