1. 概述

Spring Data 现已支持 Java 8 的核心特性——包括 OptionalStream APICompletableFuture

本文将通过几个示例,展示如何在 Spring Data 框架中使用这些特性。

2. Optional 的使用

先看 CRUD 仓库方法——现在它们会返回 Optional 包装的结果:

public interface CrudRepository<T, ID> extends Repository<T, ID> {
    
    Optional<T> findById(ID id);
    
}

返回 Optional 实例是个明确的信号:查询结果可能不存在。关于 Optional 的更多细节可参考这里

我们只需将返回类型声明为 Optional 即可:

public interface UserRepository extends JpaRepository<User, Integer> {
    
    Optional<User> findOneByName(String name);
    
}

3. Stream API 集成

Spring Data 还支持 Java 8 最重要的特性之一——Stream API

过去,当需要返回多条记录时,我们只能返回集合:

public interface UserRepository extends JpaRepository<User, Integer> {
    // ...
    List<User> findAll();
    // ...
}

这种实现有个明显问题:内存消耗大。所有查询结果必须立即加载并保存在内存中。

改进方案是使用分页:

public interface UserRepository extends JpaRepository<User, Integer> {
    // ...
    Page<User> findAll(Pageable pageable);
    // ...
}

某些场景下分页足够,但数据量巨大时,频繁分页请求反而会成为性能瓶颈。

得益于 Java 8 的 Stream API 和 JPA 提供程序——现在我们可以直接让仓库方法返回对象流

public interface UserRepository extends JpaRepository<User, Integer> {
    // ...
    Stream<User> findAllByName(String name);
    // ...
}

Spring Data 会利用提供程序特定实现(如 Hibernate 的 ScrollableResultSet 或 EclipseLink 的 ScrollableCursor)流式传输结果。这显著降低了内存消耗和数据库查询次数,性能远超前两种方案。

⚠️ 使用 Stream 处理数据后必须关闭流,可通过以下两种方式实现:

  • 调用 close() 方法
  • 使用 try-with-resources 语法:
try (Stream<User> foundUsersStream 
  = userRepository.findAllByName("张三")) {
 
    assertThat(foundUsersStream.count(), equalTo(3L));
}

务必在事务中调用仓库方法,否则会抛出异常:

org.springframework.dao.InvalidDataAccessApiUsageException: 您试图在没有保持连接打开的事务中执行流式查询方法,导致 Stream 无法被消费。请确保消费流的代码使用 @Transactional 或其他方式声明(只读)事务。

4. CompletableFuture 异步支持

Spring Data 仓库可通过 Java 8 的 CompletableFuture 和 Spring 异步执行机制实现异步操作

@Async
CompletableFuture<User> findOneByStatus(Integer status);

客户端调用此方法会立即返回 Future 对象,而实际方法执行将在另一个线程中完成。

关于 CompletableFuture 的更多细节可参考这里

5. 总结

本文展示了 Java 8 特性如何与 Spring Data 协同工作。完整示例代码可在 GitHub 获取。


原始标题:Spring Data Java 8 Support