In Spring Data JPA, a derived find method doesn't use first-level cache while called multiple times in one transaction, but the default findById does

huangapple go评论72阅读模式
英文:

In Spring Data JPA, a derived find method doesn't use first-level cache while called multiple times in one transaction, but the default findById does

问题

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

Optional<User> findUserById(Long id);

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

  • 看起来,派生方法在方法内部使用@Transactional多次调用时不会遵循一级缓存(持久化上下文),因此会产生多个select语句。
  • 另一方面,默认的findById方法使用了一级缓存,在与上述相同的条件下,实际只执行了一次select语句(后续调用获取了缓存的结果)。

即使我的自定义方法也叫做findById,同样会出现多次select的情况(但在这种情况下,我们显然不能使用默认方法,因为它被遮蔽了)。

有人知道为什么会发生这种情况吗?提前感谢。

我正在使用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;
}

用户仓库

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
	
	Optional<User> findUserById(Long id);
}

数据库服务

@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");
	}
}

调用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

调用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
英文:

I've created a custom (derived) find method in the repository:

Optional&lt;User&gt; findUserById(Long id);

Its signature is the same as the default one (findById(...)) inherited from JpaRepository but their behavior is a bit different:

  • Seems like the derived method does not respect the first-level cache (persistent context) if it's being called multiple times inside the method with @Transactional, and, as a result, we're getting multiple select statements.
  • On the other hand, the default findById method uses the first level cache, and, in the same conditions as above, only one select statement is actually executed (next calls get the cached result).

The same (multiple selects) happens even if my custom method is called findById too (but in this case we obviously cannot use the default one because it's shaded by it).

Does anybody know why this is happening? Thanks in advance.

I'm using Spring Boot 2.3.4.RELEASE / Hibernate 5.4.21.Final

Some meaningful code is below (the full project with tests can be found on Github):

User entity:

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

	@Id
	private Long id;
	private String name;
}

UserRepository:

@Repository
public interface UserRepository extends JpaRepository&lt;User, Long&gt; {

	Optional&lt;User&gt; findUserById(Long id);
}

DatabaseService:

@Service
@RequiredArgsConstructor
@Slf4j
public class DatabaseService {

	private final UserRepository userRepository;

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

	@Transactional
	public void testCustomMethod() {
		log.info(&quot;Start to call custom findUserById&quot;);
		userRepository.findUserById(1L);
		userRepository.findUserById(1L);
		userRepository.findUserById(1L);
		log.info(&quot;End to call custom findUserById&quot;);
	}
}

Output while calling 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

Output while calling 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

答案1

得分: 5

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

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

英文:

In the absence of a query cache, a custom query is always executed against the DB. JPA doesn't know what your query is asking for, so it cannot 'guess' the result. It needs to delegate to the DB.

The only time a query is not executed is when the entity has already been loaded into the context and you call EntityManager.find() directly (which is what the default repository method does under the hood).

huangapple
  • 本文由 发表于 2020年10月4日 08:51:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/64190242.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定