AOT(提前编译)优化
本章介绍 Spring Data 的 AOT(提前编译)优化,这些优化构建在 Spring 的 AOT(提前编译)优化 之上。
最佳实践
为领域类型添加注解
在应用程序启动期间,Spring 会扫描 classpath 上的领域类,以便对实体进行早期处理。通过用 Spring Data 专用的 @Table、@Document 或 @Entity 注解标注您的领域类型,您可以辅助初始实体扫描,并确保这些类型作为运行时提示(Runtime Hints)注册到 ManagedTypes 中。在原生映像的安排中无法进行 classpath 扫描,因此 Spring 必须使用 ManagedTypes 来确定初始实体集合。
运行时提示
与常规 JVM 运行时相比,以原生映像方式运行应用程序需要额外的信息。Spring Data 会在 AOT 处理期间为原生映像的使用贡献运行时提示。这些提示尤其针对:
-
审计
-
用于捕获 classpath 扫描结果的
ManagedTypes -
仓库
-
针对实体、返回类型和 Spring Data 注解的反射提示
-
仓库片段
-
Querydsl
Q类 -
Kotlin 协程支持
-
-
Web 支持(针对
PagedModel的 Jackson 提示)
AOT 仓库
AOT 仓库是 AOT 处理的一种扩展,它通过预先生成符合条件的查询方法实现来完成。对于开发者而言,查询方法在底层执行的查询是不透明的。AOT 仓库基于构建时已知的派生查询、注解查询和命名查询来贡献查询方法实现。这项优化将查询方法的处理从运行时移到构建时,由于查询方法无需在每次应用程序启动时通过反射进行分析,因此可以带来显著的性能提升。
生成的 AOT 仓库片段遵循 <Repository FQCN>Impl__Aot 的命名方案,并放置在与仓库接口相同的包中。您可以在生成的仓库查询方法中找到所有查询的字符串形式。
请将 AOT 仓库类视为内部优化。不要在您的代码中直接使用它们,因为生成和实现细节在未来版本中可能会发生变化。
使用 AOT 仓库运行
AOT 是将 Spring 应用程序转换为原生可执行文件的必经步骤,因此在这种模式下运行时会自动启用。当 AOT 启用时(无论是为了原生编译还是通过设置 spring.aot.enabled=true),AOT 仓库也会被生成。
您可以完全禁用 AOT 仓库生成,或仅禁用 JDBC AOT 仓库:
-
设置
spring.aot.repositories.enabled=false属性,可为所有 Spring Data 模块禁用生成的仓库。 -
设置
spring.aot.jdbc.repositories.enabled=false属性,可仅禁用 JDBC AOT 仓库。
AOT 仓库会对实际的仓库 bean 注册贡献配置变更,以注册生成的仓库片段。
当包含 AOT 优化时,一些在构建时所做的决策会被硬编码到应用程序设置中。例如,在构建时启用的 profile 在运行时也会自动启用。此外,实现某个仓库的 Spring Data 模块是固定的。更改实现需要重新进行 AOT 处理。
请提供 JdbcDialect,以避免因方言检测而导致的早期数据库访问。
符合条件的方法
AOT 仓库会筛选符合 AOT 处理条件的方法。这些方法通常是所有未由实现片段支持的查询方法。
支持的功能
-
派生查询方法、
@Query注解查询和命名查询方法 -
返回
void、int和long的@Modifying方法 -
分页、
Slice、Stream和Optional返回类型 -
DTO 投影和接口投影
-
值表达式
限制
- 接受
ScrollPosition的方法(例如Keyset分页)尚未受支持
被排除的方法
-
CrudRepository、Querydsl、按示例查询以及其他基础接口方法,因为它们的实现由基类对应的片段提供 -
实现过于复杂的方法
仓库元数据
AOT 处理会内省查询方法,并收集有关仓库查询的元数据。Spring Data JDBC 将这些元数据存储在 JSON 文件中,文件以仓库接口命名,并与仓库接口放在一起(即在同一个包中)。仓库 JSON 元数据包含有关查询和片段的详细信息。以下面的仓库为例:
interface UserRepository extends CrudRepository<User, Integer> {
List<User> findUserNoArgumentsBy(); 1
Page<User> findPageOfUsersByLastnameStartingWith(String lastname, Pageable page); 2
@Query("select * from User u where username = ?1")
User findAnnotatedQueryByEmailAddress(String username); 3
User findByEmailAddress(String emailAddress); 4
}
不带参数的派生查询。
使用分页的派生查询。
注解查询。
命名查询。虽然存储过程方法包含在 JSON 元数据中,但它们的方法代码块不会在 AOT 仓库中生成。
{
"name": "com.acme.UserRepository",
"module": "JDBC",
"type": "IMPERATIVE",
"methods": [
{
"name": "findUserNoArgumentsBy",
"signature": "public abstract java.util.List<com.acme.User> com.acme.UserRepository.findUserNoArgumentsBy()",
"query": {
"query": "SELECT * FROM User"
}
},
{
"name": "findPageOfUsersByLastnameStartingWith",
"signature": "public abstract org.springframework.data.domain.Page<com.acme.User> com.acme.UserRepository.findPageOfUsersByLastnameStartingWith(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": "findAnnotatedQueryByEmailAddress",
"signature": "public abstract com.acme.User com.acme.UserRepository.findAnnotatedQueryByEmailAddress(java.lang.String)",
"query": {
"query": "select * from User where emailAddress = ?1"
}
},
{
"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 = ?1"
}
},
{
"name": "count",
"signature": "public abstract long org.springframework.data.repository.CrudRepository.count()",
"fragment": {
"fragment": "org.springframework.data.jdbc.repository.support.SimpleJdbcRepository"
}
}
]
}
查询可能包含以下字段:
-
query:如果该方法是查询方法,则为查询描述符。-
name:如果是命名查询,则为命名查询的名称。 -
query:用于从EntityManager获取查询方法结果的查询。 -
count-name:如果计数查询是命名查询,则为命名计数查询的名称。 -
count-query:用于为使用分页的查询方法获取计数的计数查询。
-
-
fragment:如果方法调用被委托给某个存储(仓库基类、功能片段如 Querydsl)或用户片段,则为目标片段。如果没有进一步的接口,片段仅用fragment描述;如果存在接口(例如 Querydsl 或用户声明的片段接口),则以interface和fragment元组的形式描述。
规范化查询形式
对查询的静态分析只能表示运行时查询行为的有限部分。查询以其规范化形式(预解析并重写后的形式)表示:
-
值表达式被替换为绑定标记。
-
查询元数据不反映绑定值的处理。
StartingWith/EndingWith查询会在实际绑定值前添加/追加通配符%。 -
运行时排序信息无法纳入查询字符串本身,因为该细节在构建时是未知的。