Ohhnews

分类导航

$ cd ..
Baeldung原文

Spring Data AOT 存储库简介

#spring data#aot编译#java性能优化#spring boot#存储库

[LOADING...]

1. 概述

过去几年中,Java 生态系统在提升 JVM 性能方面投入了大量精力。我们见证了 JIT 编译器预编译 (AoT) 以及最近的 Native Images(原生镜像)技术的发展。

Micronaut 和 Spring 等主流 Java 框架也朝着同一个方向努力,通过改进编译过程来获取更好的性能。Spring 提供了 Ahead of Time (AOT)Native Image 支持。然而,在这些大型框架中,反射(Reflection)的使用依然广泛,因此仍有很大的优化空间。Spring Boot 4 引入的最新特性是针对 Spring Data 的 AOT 优化——即 AOT Repositories。

在本文中,我们将探讨 Spring Data AOT Repositories 带来的新变化。由于该功能尚未在任何正式发布版本中提供,我们将使用里程碑版本进行演示。最后,我们将通过对比 Spring Boot 3(包含 AOT 与不包含 AOT)的现有方案,展示其工作原理。

2. 依赖项

首先,我们定义演示 Spring Data AOT Repositories 所需的最小依赖项。只需保留 spring-boot-starter-webspring-boot-starter-data-jpa。前者用于提供端点以验证 Repository 是否正常工作,后者包含了 Spring 与数据库交互所需的一切:

$ xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>4.0.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>4.0.5</version>
</dependency>

Spring Boot 4 主版本目前处于早期阶段,我们将使用稳定的 4.0.5 版本进行演示。

3. Spring Data Repository

接下来,我们看看 Repository 的实现是如何从运行时代理演变为由 Spring Data AOT Repositories 在构建时生成的代码。我们将使用一个经典且简单的 User 实体及其对应的 UserRepository 作为示例,对比三种 Repository 的行为和性能:无 AOT、有 AOT(但在 AOT Repositories 之前)以及 AOT Repositories。

3.1. 领域模型

我们使用一个包含三个字段的短 User 实体进行演示:

$ java
@Entity
@Table(name = "USERS")
public class User {
    @Id
    @GeneratedValue
    private Long id;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    // 构造函数、setter、getter、equals 等
}

实体类仅包含 idfirstNamelastName 字段。我们可以使用熟悉的 Spring Data 和 JPA 注解。接着创建相关的 UserRepository

$ java
public interface UserRepository extends Repository<User, Long> {
    User save(User user);
    @Transactional(readOnly = true)
    List<User> findAll();
    List<User> findAllById(Iterable<Long> longs);
    @Query(value = "SELECT * FROM users", nativeQuery = true)
    List<User> nativeQueryFindAllUsers();
    @Query(value = "SELECT u FROM User u")
    List<User> queryFindAllUsers();
}

首先,我们包含了三个 Spring Data 可以自动转换为查询的方法。此外,我们添加了两个带有 @Query 注解的方法(一个原生,一个非原生),以观察 Spring Data AOT Repository 如何处理不同情况。

其次,我们扩展了 Repository 接口以使其更通用。前面包含的依赖项会将其转换为 JpaRepository

3.2. 无 AOT 的 Spring Data Repositories

在 Spring AOT 优化和 Spring Data AOT Repositories 出现之前,我们通过 mvn clean install 命令编译代码,这会将 Java 文件转换为类,除了缩进外没有任何改变。

这意味着实现代码并非预先生成,正如我们所知,一切都发生在运行时:

  1. Spring 找到 Repository 接口。
  2. 将其传递给 JpaRepositoryFactory
  3. JpaRepositoryFactory 构建 SimpleJpaRepository 实例。
  4. 使用动态代理包装每个实例,将接口方法调用(如 findAll())路由到 SimpleJpaRepository 的对应方法、QueryJPQL 实现,或任何自定义实现。

一切都在运行时基于反射构建,这会影响启动时间和内存消耗。

3.3. 有 AOT 但无 AOT Repositories 的情况

Spring 6 引入了预处理优化。要启用此功能,我们需要:

  • 编译代码时包含 spring-boot:process-aot 任务,或设置构建插件默认启用 AOT 编译。
  • 设置 spring.aot.enabled 属性为 true 来执行应用程序。

对于 Spring Data Repositories,其结果与之前相同。Spring 依然在运行时使用 SimpleJpaRepository、反射和代理。区别在于 AOT 编译创建了一个额外的类 UserRepository__BeanDefinitions,其中包含在构建时预计算的反射元数据和其他信息,这改善了启动时间。

3.4. Spring Data AOT Repositories

Spring Data 4 引入的新特性是 Spring Data AOT Repositories。有了这个功能,Spring 将应用程序启动时执行的所有 Repository 准备工作转移到了构建时。

简而言之,Spring 利用存储库的特定性质,将 Repository 查询方法转化为实际的源代码。已实现的方法(如 SimpleJpaRepository 中的 save())被直接使用,Spring 无需为这些方法生成代码。而其余方法将在 UserRepositoryImpl__AotRepository 类中实现。

编译代码的方式与之前的 Spring 版本相同。我们可以在 target/classes 文件夹中找到生成的类:

$ java
@Generated
public class UserRepositoryImpl__AotRepository extends AotRepositoryFragmentSupport {
    private final RepositoryFactoryBeanSupport.FragmentCreationContext context;
    private final EntityManager entityManager;
    public List<User> nativeQueryFindAllUsers() {
        String var1 = "SELECT * FROM users";
        Query var2 = this.entityManager.createNativeQuery(var1, User.class);
        return var2.getResultList();
    }
    public List<User> queryFindAllUsers() {
        String var1 = "SELECT u FROM User u";
        Query var2 = this.entityManager.createQuery(var1);
        return var2.getResultList();
    }
}

带有 @Query 注解的方法包含了如果我们不使用 Spring Data 时会编写的代码。 UserRepository__BeanDefinitions 类依然存在。此外,该特性还会生成一个包含 native-image 提示的 JSON 文件 UserRepository.json

通过这种方式,Spring 在构建时生成了具体的 Repository 实现,消除了代理和大部分运行时反射,从而显著改善了启动时间和内存使用。

要启用 Spring Data AOT Repositories,需要将 spring.aot.repositories.enabled 属性设置为 true

$ bash
mvn spring-boot:run -Dspring.aot.enabled=true -Dspring.aot.repositories.enabled=true

4. 性能影响

Spring Data AOT Repositories 预计将提升应用程序性能,缩短启动时间并降低内存消耗。

4.1. 构建时间对比

由于 AOT Repositories 在构建时执行了额外的工作来构建 Repository,因此构建时间会变长:

  • 无 AOT 的 Repository:Total time: 11.076 s
  • 有 AOT 的 Repository:Total time: 17.166 s
  • AOT Repositories:Total time: 25.390 s

使用 Spring Data AOT Repositories,如果 JPA 方法中存在语法错误,我们会在编译时看到错误。 这是因为 JPA 方法到 SQL 语句的转换现在发生在编译时而非运行时。

4.2. 启动时间对比

通过脚本统计服务启动并准备好响应请求的时间:

  • 无 AOT 的 Repository:time elapsed 10148 millis
  • 有 AOT 的 Repository:time elapsed 9885 millis
  • AOT Repositories:time elapsed 8745 millis

这符合预期,因为 AOT 和 AOT Repositories 的主要目标正是缩短启动时间。

4.3. 负载下的性能对比

在负载测试中,虽然内存改进未在小型项目中显著体现,但启动速度的提升是明确的。

5. 结论

在本文中,我们探讨了 Spring Boot 4 的新特性——Spring Data AOT Repositories。通过简单的 UserRepository,我们展示了它是如何通过在构建时生成代码来消除运行时反射和代理的。尽管构建时间有所增加,但其带来的启动速度提升在现代化应用中具有重要价值。

示例源代码可在 GitHub 上找到。