[英]JPA unidirectional @OneToOne relationship with shared primary key always trigger a secondary query even if fetchType is EAGER
I am building a blog system, and like to provide the upvote/downvote feature for the blog.我正在构建一个博客系统,并希望为博客提供 upvote/downvote 功能。 Since the vote count number of blog should be persisted, i choose to use MySQL to act as the data store.由于博客的投票数应该被持久化,我选择使用 MySQL 作为数据存储。 And i use Spring JPA(Hibernate) to do the ORM job.我使用 Spring JPA(Hibernate) 来完成 ORM 工作。 Here's my data objects:这是我的数据对象:
class Blog{
// ...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToOne(optional = false, fetch = FetchType.EAGER)
@PrimaryKeyJoinColumn
private BlogVoteCounter voteCounter;
}
And the counter class:和计数器 class:
@Entity
public class BlogVoteCounter extends ManuallyAssignIdEntitySuperClass<Long> {
@Id
private Long id;
private Integer value;
}
The reason why i separate the BlogVoteCounter
from Blog
is that i think the voteCount
field will be modified by a totally different frequency comparing to other fields of Blog, since i want to use cache to cache the Blog
, following this guide , i choose to separate them.我将BlogVoteCounter
与Blog
分开的原因是,我认为与 Blog 的其他字段相比, voteCount
字段的修改频率将完全不同,因为我想使用缓存来缓存Blog
,按照本指南,我选择分开他们。
However, since the VoteCount
field might be always needed when return the Blog object to the front end, and to avoid the n+1 problem, i declared the BlogVoteCounter
field in Blog class with EAGER fetch type.但是,由于在将博客 object 返回到前端时可能总是需要VoteCount
字段,并且为了避免 n+1 问题,我在博客 class 中声明了BlogVoteCounter
字段为 EAGER 获取类型。
I've already seen this article .我已经看过这篇文章了。 Thus according to my personal comprehension, i use unidirectional relationship and only declare OneToOne
in the Blog
side.因此,根据我个人的理解,我使用单向关系,并且只在Blog
端声明OneToOne
。
However, when i examine the query, it turns out that jpa will still trigger a secondary query to retrieve BlogVoteCounter
from database without simply using a join when use findAll
method on BlogRepository
.但是,当我检查查询时,事实证明 jpa 仍将触发辅助查询以从数据库中检索BlogVoteCounter
,而无需在BlogRepository
上使用findAll
方法时简单地使用连接。
select
blogvoteco0_.id as id1_2_0_,
blogvoteco0_.value as value2_2_0_
from
blog_vote_counter blogvoteco0_
where
blogvoteco0_.id=?
So how should i config, to always make the BlogVoteCounter
field in Blog
be fetched eagerly.那么我应该如何配置,始终使Blog
中的BlogVoteCounter
字段被热切地获取。
The usage of ManuallyAssignIdEntitySuperClass
is following the Spring JPA doc , since i manually assign id for BlogVoteCounter
class. ManuallyAssignIdEntitySuperClass
的用法遵循 Spring JPA doc ,因为我手动为BlogVoteCounter
class 分配 id。
@MappedSuperclass
public abstract class ManuallyAssignIdEntitySuperClass<ID> implements Persistable<ID> {
@Transient
private boolean isNew = true;
@Override
public boolean isNew() {
return isNew;
}
@PrePersist
@PostLoad
void markNotNew(){
this.isNew = false;
}
}
And the BlogRepository
is derived from JpaRepository
而BlogRepository
是从JpaRepository
派生的
public interface BlogRepository extends JpaRepository<Blog, Long>{
// ...
}
I trigger the query by using findAll
method, but using findById
or other conditional query seems no difference.我使用findAll
方法触发查询,但使用findById
或其他条件查询似乎没有区别。
When to fetch
vsHow to fetch
:fetchType
defines when to fetch the association (instantly
vslater when someone access
) the association but not how to fetch the association(ie second select vs join query).When to fetch
与How to fetch
:fetchType
定义何时获取关联(later when someone access
instantly
与稍后)关联,但不定义如何获取关联(即第二个 select 与加入查询)。 So from JPA Spec point of view, EAGER means dont wait until someone access that field to populate it but JPA provider is free to use JOIN or second select as long as they do it immediately.因此,从 JPA 规范的角度来看,EAGER 意味着不要等到有人访问该字段来填充它,但 JPA 提供者可以免费使用 JOIN 或第二个 Z99938282F04071859941E18F16EF,只要他们立即执行它。
Even though they are free to use join vs second select, still I thought they should have optimised for join in the case of EAGER.尽管他们可以免费使用 join 与第二个 select,但我仍然认为他们应该针对 EAGER 的情况进行优化。 So interested in finding out the logical reasoning for not using the join非常有兴趣找出不使用连接的逻辑推理
1. Query generated for repository.findById(blogId);
1.为repository.findById(blogId);
select
blog0_.id as id1_0_0_,
blog0_.vote_counter_id as vote_cou2_0_0_,
blogvoteco1_.id as id1_1_1_,
blogvoteco1_.value as value2_1_1_
from
blog blog0_
inner join
blog_vote_counter blogvoteco1_
on blog0_.vote_counter_id=blogvoteco1_.id
where
blog0_.id=?
2. Updated Mapping 2.更新映射
public class Blog {
@Id
private Long id;
@ManyToOne(optional = false, cascade = ALL, fetch = FetchType.EAGER)
@PrimaryKeyJoinColumn
private BlogVoteCounter voteCounter;
public Blog() {
}
public Blog(Long id, BlogVoteCounter voteCounter) {
this.id = id;
this.voteCounter = voteCounter;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public BlogVoteCounter getVoteCounter() {
return voteCounter;
}
public void setVoteCounter(BlogVoteCounter voteCounter) {
this.voteCounter = voteCounter;
}
}
3. Issues with current Mapping 3. 当前映射的问题
blog
and votecounter
as it causes a chicken and egg
problem.根据您的映射,创建blog
和votecounter
是不可能的,因为它会导致先有chicken and egg
的问题。 ie IE4.Changes 4.变化
5.Notes 5.注意事项
repository.findAll
was generating 2 queries so I overridden that method to generate one join query默认repository.findAll
正在生成 2 个查询,因此我重写了该方法以生成一个联接查询public interface BlogRepository extends JpaRepository<Blog, Long> {
@Override
@Query("SELECT b from Blog b join fetch b.voteCounter ")
List<Blog> findAll();
}
select
blog0_.id as id1_0_0_,
blogvoteco1_.id as id1_1_1_,
blog0_.vote_counter_id as vote_cou2_0_0_,
blogvoteco1_.value as value2_1_1_
from
blog blog0_
inner join
blog_vote_counter blogvoteco1_
on blog0_.vote_counter_id=blogvoteco1_.id
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.