提前优化(AOT)
本章介绍 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查询会在实际绑定值前/后添加通配符%。 -
运行时排序信息无法并入查询字符串本身,因为该细节在构建时未知。