简体   繁体   English

JPA 与共享主键的单向@OneToOne 关系始终触发辅助查询,即使 fetchType 为 EAGER

[英]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.我将BlogVoteCounterBlog分开的原因是,我认为与 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 JpaRepositoryBlogRepository是从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 vs How to fetch : fetchType defines when to fetch the association ( instantly vs later when someone access ) the association but not how to fetch the association(ie second select vs join query). When to fetchHow to fetchfetchType定义何时获取关联( 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. 当前映射的问题

  • As per your mapping, it is impossible to create blog and votecounter as it causes a chicken and egg problem.根据您的映射,创建blogvotecounter是不可能的,因为它会导致先有chicken and egg的问题。 ie IE
  • blog and votecounter need to share the same primary key blog 和 votecounter 需要共享同一个主键
  • blog's primary key is generated by database.博客的主键是由数据库生成的。
  • so in order to get the primary key of blog and assign it to votecounter as well, you need to store blog first所以为了获取博客的主键并将其分配给投票计数器,您需要先存储博客
  • but the @OneToOne relationship is not optional, so you cannot store blog first alone但是@OneToOne 关系不是可选的,所以你不能先单独存储博客

4.Changes 4.变化

  • Either need to make the relationship optional so blog can be stored first, get the id, assign to BlogVoteCounter and save the counter要么需要使关系可选,因此可以先存储博客,获取 id,分配给 BlogVoteCounter 并保存计数器
  • Or Don't auto generate Id and manually assign the id so blog and votecounter can be saved at the same time.(I have gone for this option but you can do first option)或者不要自动生成 ID 并手动分配 ID,这样可以同时保存博客和投票计数器。(我已经选择了这个选项,但你可以做第一个选项)

5.Notes 5.注意事项

  • default 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM