在 Spring Data JPA 中,派生的 find 方法在一个事务中多次调用时不使用一级缓存,但默认的 findById 会使用一级缓存

ams*_*ger 3 java spring hibernate spring-data-jpa spring-boot

我在存储库中创建了一个自定义(派生)查找方法:

Optional<User> findUserById(Long id);
Run Code Online (Sandbox Code Playgroud)

findById(...)它的签名与继承自的默认签名 ( ) 相同,JpaRepository但它们的行为有点不同:

  • 如果派生方法在方法内部使用 多次调用,则派生方法似乎不尊重一级缓存(持久上下文)@Transactional,因此,我们会得到多个 select 语句。
  • 另一方面,默认findById方法使用一级缓存,并且在与上述相同的条件下,实际上只执行一条 select 语句(接下来的调用获取缓存的结果)。

即使我的自定义方法也被调用,也会发生相同的(多个选择)findById(但在这种情况下,我们显然不能使用默认方法,因为它被它遮蔽了)。

有谁知道为什么会发生这种情况?提前致谢。

我正在使用 Spring Boot 2.3.4.RELEASE / Hibernate 5.4.21.Final

下面是一些有意义的代码(带有测试的完整项目可以在Github上找到):

用户实体

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @Id
    private Long id;
    private String name;
}
Run Code Online (Sandbox Code Playgroud)

用户存储库

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    Optional<User> findUserById(Long id);
}
Run Code Online (Sandbox Code Playgroud)

数据库服务

@Service
@RequiredArgsConstructor
@Slf4j
public class DatabaseService {

    private final UserRepository userRepository;

    @Transactional
    public void testDefaultMethod() {
        log.info("Start to call default findById");
        userRepository.findById(1L);
        userRepository.findById(1L);
        userRepository.findById(1L);
        log.info("End to call default findById");
    }

    @Transactional
    public void testCustomMethod() {
        log.info("Start to call custom findUserById");
        userRepository.findUserById(1L);
        userRepository.findUserById(1L);
        userRepository.findUserById(1L);
        log.info("End to call custom findUserById");
    }
}
Run Code Online (Sandbox Code Playgroud)

调用时输出testDefaultMethod()

2020-10-04 03:11:26.379  INFO 19264 --- [           main] c.e.f.domain.DatabaseService             : Start to call default findById
Hibernate: select user0_.id as id1_0_0_, user0_.name as name2_0_0_ from user user0_ where user0_.id=?
2020-10-04 03:11:26.388  INFO 19264 --- [           main] c.e.f.domain.DatabaseService             : End to call default findById
Run Code Online (Sandbox Code Playgroud)

调用时输出testCustomMethod()

2020-10-04 03:11:26.399  INFO 19264 --- [           main] c.e.f.domain.DatabaseService             : Start to call custom findUserById
Hibernate: select user0_.id as id1_0_, user0_.name as name2_0_ from user user0_ where user0_.id=?
Hibernate: select user0_.id as id1_0_, user0_.name as name2_0_ from user user0_ where user0_.id=?
Hibernate: select user0_.id as id1_0_, user0_.name as name2_0_ from user user0_ where user0_.id=?
2020-10-04 03:11:26.500  INFO 19264 --- [           main] c.e.f.domain.DatabaseService             : End to call custom findUserById
Run Code Online (Sandbox Code Playgroud)

cri*_*zis 7

在没有查询缓存的情况下,始终针对数据库执行自定义查询。JPA 不知道您的查询要求什么,因此它无法“猜测”结果。它需要委托给数据库。

唯一不执行查询的情况是当实体已加载到上下文中并且您EntityManager.find()直接调用时(这是默认存储库方法在后台执行的操作)。