预先优化

本章介绍 Spring Data 的预编译(Ahead of Time,AOT)优化,这些优化基于Spring 的预编译优化spring-doc.cadn.net.cn

最佳实践

注解您的领域类型

在应用程序启动期间,Spring 会扫描类路径中的领域类,以便提前处理实体。 通过使用 Spring Data 特定的 @Table@Document@Entity 注解对您的领域类型进行标注,您可以辅助初始的实体扫描,并确保这些类型被注册到 ManagedTypes 中以提供运行时提示(Runtime Hints)。 在原生镜像(native image)环境中无法进行类路径扫描,因此 Spring 必须使用 ManagedTypes 来确定初始的实体集合。spring-doc.cadn.net.cn

运行时提示

与常规的 JVM 运行时相比,以原生镜像(native image)方式运行应用程序需要额外的信息。 Spring Data 在 AOT(提前编译)处理过程中会为原生镜像的使用提供运行时提示(Runtime Hints)。 这些提示尤其包括:spring-doc.cadn.net.cn

提前编译存储库

AOT 仓库是对 AOT 处理的一种扩展,通过预生成符合条件的查询方法实现。 对于开发者而言,查询方法在其调用时所执行的底层查询是不透明的。 AOT 仓库基于在构建时已知的派生查询、注解查询和命名查询,提供查询方法的实现。 此优化将查询方法的处理从运行时移至构建时,从而显著提升性能,因为应用程序每次启动时不再需要通过反射来分析查询方法。spring-doc.cadn.net.cn

生成的 AOT 仓库片段遵循 <Repository FQCN>Impl__Aot 的命名规则,并放置在与仓库接口相同的包中。 您可以在其中找到所有为生成的仓库查询方法所对应的字符串形式的查询语句。spring-doc.cadn.net.cn

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

使用 AOT 仓库运行

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

你可以完全禁用 AOT 仓库生成,或者仅禁用 Cassandra AOT 仓库:spring-doc.cadn.net.cn

  • 设置 spring.aot.repositories.enabled=false 属性以禁用所有 Spring Data 模块的生成仓库。spring-doc.cadn.net.cn

  • 设置 spring.aot.cassandra.repositories.enabled=false 属性以仅禁用 Cassandra AOT 仓库。spring-doc.cadn.net.cn

AOT 仓库向实际的仓库 Bean 注册贡献配置更改,以注册生成的仓库片段。spring-doc.cadn.net.cn

当包含AOT(提前编译)优化时,一些在构建时做出的决策会被硬编码到应用程序的配置中。 例如,在构建时启用的配置文件(profiles)在运行时也会自动启用。 此外,实现仓库(repository)的Spring Data模块也被固定下来。 如需更改实现,必须重新进行AOT处理。

符合条件的

AOT 仓库会筛选出符合 AOT 处理条件的方法。 这些方法通常是所有未由实现片段支持的查询方法。spring-doc.cadn.net.cn

支持的功能spring-doc.cadn.net.cn

排除的方法spring-doc.cadn.net.cn

仓库元数据

AOT 处理会内省查询方法,并收集有关仓库查询的元数据。 Spring Data Cassandra 将这些元数据存储在 JSON 文件中,文件名与仓库接口相同,并与其存放在一起(即位于同一个包内)。 仓库 JSON 元数据包含有关查询和片段的详细信息。 以下展示了针对下列仓库的一个示例:spring-doc.cadn.net.cn

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)
}
1 无参数的派生查询。
2 使用分页的派生查询。
3 带注解的查询。
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"
      }
    }
  ]
}

查询可能包含以下字段:spring-doc.cadn.net.cn

  • query:如果该方法是一个查询方法,则为查询描述符。spring-doc.cadn.net.cn

  • fragment:如果方法调用被委托给一个存储库(repository 基类、函数式片段如 Querydsl)或用户自定义片段,则此为对应的目标片段。 如果没有其他接口,片段仅用 fragment 表示;如果存在接口(例如 Querydsl 或用户声明的片段接口),则片段以 interfacefragment 的元组形式描述。spring-doc.cadn.net.cn

标准化查询表单

对查询的静态分析仅能有限地表示运行时查询行为。 查询以其规范化(预解析并重写)的形式表示:spring-doc.cadn.net.cn

  • 值表达式将被替换为绑定标记。spring-doc.cadn.net.cn

  • 查询元数据不反映绑定值的处理过程。 StartingWith/EndingWith 查询会在实际绑定值的前面/后面添加通配符 %spring-doc.cadn.net.cn

  • 运行时的排序信息无法直接包含在查询字符串中,因为该细节在构建时是未知的。spring-doc.cadn.net.cn