Spring Data 预编译仓库 - 第 2 部分

工程 | Christoph Strobl | 2025年11月25日 | ...

总结 Road to GA 博客系列,让我们探讨 Spring Data AOT Repositories 的好处。

早在 2025 年 5 月,我们首次将 预编译(AOT)仓库 作为 JPA 和 MongoDB 的预览功能引入,同时发布了 下一代 Spring Data 的第三个里程碑。简而言之,此功能利用 AOT 处理,通过依赖仓库的特定存储特性,使用实际源代码实现你的仓库查询方法。

从那时起,我们吸取了反馈意见,解决了粗糙之处,并引入了另外两个模块:Apache Cassandra 和 JDBC。这意味着,随着 2025.1.0 版本的发布,您将能够使用AOT生成的仓库与四个Spring Data模块一起使用

您将得到什么

通过利用通用的 Spring AOT 基础设施,我们对您的应用程序配置有了深入的了解,从而实现了比使用注解处理器更好的集成。AOT 处理的预初始化应用程序上下文配置使我们能够访问所有已注册的 bean、它们的类型和属性,捕获您的意见和配置。正如您所能想象的,当您希望与 Data JDBC 等集成一起工作时,这种洞察力至关重要:虽然目标是标准 SQL,但 JDBC 模块需要了解目标数据库的潜在特殊性,这些特殊性通常伴随着它们自己的方言偏差。访问已注册的 bean(如 JdbcDialect)使得生成正确的语句成为可能。

因此,当我们查看生成的代码时,它与各个模块共享共同的原则,同时尽可能地贴近底层技术。

正如每个 Spring Data 模块都特定于其目标技术一样,生成的代码也是如此。一个简单的查询方法,如 JDBC 中的 findByLastnameStartingWith,与为 JPAApache Cassandra 生成的方法截然不同。

Spring Data JDBC

public List<User> findByLastnameStartingWith(String lastname) {
  Criteria criteria = Criteria.where("lastname").like(escape(lastname) + "%");
  StatementFactory.SelectionBuilder builder = getStatementFactory().select(User.class).filter(criteria);

  RowMapper rowMapper = getRowMapperFactory().create(User.class);
  List result = (List) builder.executeWith((sql, paramSource) -> getJdbcOperations().query(sql, paramSource, new RowMapperResultSetExtractor<>(rowMapper)));
  return (List<User>) convertMany(result, User.class);
}

Spring Data JPA

public List<User> findByLastnameStartingWith(String lastname) {
  String queryString = "SELECT u FROM example.springdata.aot.User u WHERE u.lastname LIKE :lastname ESCAPE '\\'";
  Query query = entityManager.createQuery(queryString);
  query.setParameter("lastname", "%s%%".formatted(lastname));

  return (List<User>) query.getResultList();
}

Spring Data for Apache Cassandra

public List<User> findByLastnameStartingWith(String lastname) {
  Query query = Query.query(Criteria.where("lastname").like(lastname + "%"));

  ExecutableSelectOperation.TerminatingSelect<User> select = operations.query(User.class).matching(query);
  return select.all();
}

在构建时启用通用 Spring AOT 支持,AOT 仓库会默认生成并挂载。如果您希望,可以通过 spring.aot.repositories.enabled=false 选择不生成仓库代码,或者选择单个模块,如 spring.aot.jdbc.repositories.enabled=false

要实际使用它们,您的应用程序必须在 AOT 模式下启动(可以通过使用 spring.aot.enabled 属性或作为 GraalVM 本机镜像)。

但是,我们也要看看开发和调试的一些优势。

可调试性和元数据

AOT 生成的仓库不仅在代码中解释了查询的执行方式,您还可以在 IDE 中设置断点以在需要时调试语句。

顺着在 AOT 处理期间了解查询外观的思路,我们为您的 Spring Data 接口引入了 JSON 元数据表示。就像上面提到的仓库一样,我们暂时称之为 UserRepository,Spring Data 会生成一个 UserRepository.json 资源,该资源与您的仓库接口位于同一包中。该文件包含有关查询方法、它们所针对的实现以及它们将运行的查询的信息。它可用于文档目的,但也可以被您喜欢的 IDE 读取,以便在您开发时显示附加信息。

Spring Data JDBC

{
 "name": "example.springdata.UserRepository",
 "module": "JDBC",
 "type": "IMPERATIVE",
 "methods": [
 {
   "name": "findByLastnameStartingWith",
   "signature": "public abstract List<User> UserRepository.findByLastnameStartingWith(String)",
   "query": {
     "query": "SELECT 'USER'.'ID' AS 'ID', 'USER'.'LAST_NAME' AS 'LAST_NAME', 'USER'.'FIRST_NAME' AS 'FIRST_NAME' FROM 'USER' WHERE 'USER'.'LASTNAME' LIKE :name"
   }
 }
 //...

Spring Data JPA

{
 // ...
 "query": {
   "query": "SELECT u FROM example.springdata.User u WHERE u.lastname LIKE ?1 ESCAPE '\\'"
 }
}

Spring Data for Apache Cassandra

{
 // ...
 "query": {
   "query": "SELECT * FROM users WHERE last_name LIKE ?"
 }
}

您可以通过设置 spring.aot.repositories.metadata.enabled=false 来选择性地禁用元数据生成。

AOT 缓存和 Leyden 项目

提前生成仓库代码不仅具有可调试性的优势,而且与最新 Java 版本中的最新发展非常契合。与运行时生成的使事物正常工作的代码片段不同,预生成的仓库可以在 AOT 缓存训练运行期间被 JVM 看到和分析。这有助于您通过从 Java 共享对象文件提供已完全加载和链接的仓库实现来减少启动时间。

[info][class,load] example.springdata.aot.UserRepositoryImpl__AotRepository source: shared objects file

Spring Data NoSQL 实现的一个众所周知的特性是它们的**对象映射**功能。不为人知的是,在运行时,Spring Data 会尝试通过生成使用 MethodHandle 的字节码来优化新实例的创建和域对象的属性访问,从而提高加载/保存性能。

收集有关您的域模型的信息为生成的字节码的包含铺平了道路,这些字节码用于构建时属性访问器和实体实例化器。拥有已生成的字节码消除了使用运行时优化的需要。此外,捕获的实体实例化器和属性访问器被打包到捆绑的 JAR 中,因此可以直接通过类加载器提供,从而更有效地提供服务。

[info][class,load] example.springdata.User__Instantiator_cu2hga source: shared objects file
[info][class,load] example.springdata.User__Accessor_cu2hga source: shared objects file

有什么缺点吗?

显然,计算时间不会简单地消失,它会转移到其他地方。在这种情况下,会转移到构建阶段。所以,是的,分析应用程序和准备数据,以及进行 JVM 训练运行需要时间,并且可能需要更改您的构建和部署管道。您还需要用框架的某些动态方面来换取更少的内存消耗和更快的启动时间。当选择 AOT 时,您的部分配置需要被冻结。还记得之前提到的 JdbcDialect 吗?这正是其中一个冻结点,因为您不能为**一个**数据库生成优化的 SQL,然后切换到**另一个**数据库,就好像什么都没改变一样。

此外,目前提前(Ahead of Time)仓库仅支持命令式仓库接口。响应式接口不会触发任何代码生成。

如果您好奇,可以直接使用 spring.aot.enabled 构建并运行您的应用程序,或者前往我们的 Spring Data 示例,我们已经为您准备了演示应用程序供您尝试。

无论如何,您始终可以回退到不带 spring.aot.enabled 标志启动应用程序,这将禁用所有即时增强。这使您可以在 AOT 模式下构建和测试应用程序,同时内置了回退策略。

我们很高兴看到您如何利用即时功能。
并且,一如既往,您的反馈至关重要!因此,我们期待听到您关于改进方法的意见和经验。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有