有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

具有共享主键的java JPA单向@OneToOne关系始终会触发辅助查询,即使fetchType是急切的

我正在构建一个博客系统,并且喜欢为博客提供向上投票/向下投票功能。由于博客的计票数应该保持不变,所以我选择使用MySQL作为数据存储。我使用Spring JPA(Hibernate)来完成ORM工作。以下是我的数据对象:

class Blog{
    // ...
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @OneToOne(optional = false, fetch = FetchType.EAGER)
    @PrimaryKeyJoinColumn
    private BlogVoteCounter voteCounter;    
}

还有柜台:

@Entity
public class BlogVoteCounter extends ManuallyAssignIdEntitySuperClass<Long> {
    @Id
    private Long id;

    private Integer value;
}

我之所以将BlogVoteCounterBlog分开,是因为我认为voteCount字段的修改频率与博客的其他字段完全不同,因为我想使用缓存来缓存Blog,在这个guide之后,我选择将它们分开

然而,由于在将Blog对象返回前端时可能总是需要VoteCount字段,并且为了避免n+1问题,我在Blog类中用EAGER fetch类型声明了BlogVoteCounter字段

我已经看过了。因此,根据我个人的理解,我使用单向关系,只在Blog端声明OneToOne

然而,当我检查查询时,发现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=?

因此,我应该如何配置,以便总是急切地获取Blog中的BlogVoteCounter字段


ManuallyAssignIdEntitySuperClass的用法是在Spring JPA doc之后使用的,因为我手动为BlogVoteCounter类分配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;
    }
}

BlogRepository是从JpaRepository派生出来的

public interface BlogRepository extends JpaRepository<Blog, Long>{
    // ...
}

我使用findAll方法触发查询,但使用findById或其他条件查询似乎没有什么区别


共 (1) 个答案

  1. # 1 楼答案

    When to fetch vs How to fetch : fetchType defines when to fetch the association ( instantlyvs later when someone access) the association but not how to fetch the association(i.e second select vs join query). 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.

    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. So interested in finding out the logical reasoning for not using the join

    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。更新的地图

    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。当前映射的问题

    • 根据您的映射,不可能创建blogvotecounter,因为它会导致chicken and egg问题。 i、 e
    • blog和votecounter需要共享相同的主键
    • 博客的主键由数据库生成
    • 因此,为了获取blog的主键并将其分配给votecounter,您需要首先存储blog
    • 但是@OneToOne关系不是可选的,所以你不能单独存储博客

    4。变化

    • 或者需要将关系设置为可选,以便可以首先存储blog,获取id,分配给BlogVoteCounter并保存计数器
    • 或者不要自动生成Id并手动分配Id,这样博客和votecounter就可以同时保存。(我选择了这个选项,但你可以选择第一个选项)

    5。注意事项

    • 默认的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