预先优化

本章介绍了 Spring Data 的提前(AOT)优化,这些优化基于 Spring 的提前优化

最佳实践

注解您的领域类型

在应用程序启动期间,Spring 会扫描类路径中的域类以进行早期实体处理。通过使用 Spring Data 特定的 @Table@Document@Entity 注解来标注您的域类型,您可以帮助初始实体扫描,并确保这些类型在 ManagedTypes 中注册以用于运行时提示。在原生镜像环境中无法进行类路径扫描,因此 Spring 必须使用 ManagedTypes 来处理初始实体集。

运行时提示

将应用程序作为原生镜像运行需要比常规 JVM 运行时更多的信息。Spring Data 在 AOT 处理期间为原生镜像使用贡献了 运行时提示。这些提示特别适用于

  • 审计

  • ManagedTypes 用于捕获类路径扫描的结果

  • Repositories

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

    • Repository 片段

    • Querydsl Q

    • Kotlin 协程支持

  • Web 支持(PagedModel 的 Jackson 提示)

提前(AOT)Repository

AOT Repository 是 AOT 处理的扩展,通过预生成符合条件的查询方法实现。查询方法对开发人员来说是不透明的,因为它隐藏了在查询方法调用中执行的底层查询。AOT Repository 基于在构建时已知的派生、注解和命名查询来贡献查询方法实现。这种优化将查询方法处理从运行时移至构建时,这可以显著提高性能,因为查询方法无需在每次应用程序启动时进行反射分析。

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

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

使用 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 优化时,一些在构建时做出的决定会被硬编码到应用程序设置中。例如,在构建时启用的配置文件在运行时也会自动启用。此外,实现仓库的 Spring Data 模块是固定的。更改实现需要重新进行 AOT 处理。
请提供 JdbcDialect 以避免方言检测导致的早期数据库访问。

符合条件的方法

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

支持的功能

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

  • 返回 voidintlong@Modifying 方法

  • 分页、SliceStreamOptional 返回类型

  • DTO 和接口投影

  • 值表达式

限制

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

排除的方法

  • CrudRepository、Querydsl、Query by Example 以及其他基本接口方法,因为它们的实现由基类各自的片段提供

  • 实现过于复杂的方法

Repository 元数据

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)
}
1 无参数的派生查询。
2 使用分页的派生查询。
3 带注解的查询。
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 或用户声明的片段接口),则以 interfacefragment 元组形式描述。

规范化查询形式

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

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

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

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

© . This site is unofficial and not affiliated with VMware.