【问题标题】:Why hibernate perform two queries for eager load a @OneToOne bidirectional association?为什么休眠执行两个查询以急切加载@OneToOne 双向关联?
【发布时间】:2011-03-23 19:56:05
【问题描述】:

我有一个实体 A,它有一个 B 实体,而 B 有一个带有 @OneToOne 双向关联的 A。

现在,当我找到所有 A 记录时,hibernate 使用 B 上的左外连接执行两个查询,如下所示:

select a.id, a.id_b, a.field1, b.id, b.field1 from A as a, B as b left outer join b ON b.id=a.id_b;
select a.id, a.id_b, a.field1, b.id, b.field1 from A as a, B as b left outer join b ON b.id=a.id_b WHERE b.id=?

第一次查询加载 A 和 B 字段并可以,但是为什么要执行第二次查询来重新加载 A? 我认为这个查询将 A 内容加载到 B 中,但是这个 A 显然是包含 B 的 A...所以它已经加载了第一个查询,不是吗?

-- 编辑--

实体 A:

@Entity
public class A implements Serializable{
    // id and other ecc ecc
    @OneToOne
    @JoinColumn(name="id_b")
    B b;
}

实体 B:

@Entity
public class B implements Serializable{
    // id and other ecc ecc
    @OneToOne(mappedBy="b")
    A a;
}

就是这种情况,A 上的 findAll 需要两个查询...为什么?

【问题讨论】:

    标签: java hibernate jpa one-to-one bidirectional-relation


    【解决方案1】:

    您的映射到底是什么样的?

    您的AB 类是否正确实现了hashCode()equals(),以便Hibernate 可以判断B 指向的A 实例与第一个A 的实例相同?

    听起来您正在尝试对双向一对一映射进行建模 - 请查看 the section in the manual on this 以了解实现它的推荐方法。

    【讨论】:

    • 我编辑了我的帖子。是的,我有一个非常简单的基于 ID 的 equals 和 hashCode 实现。我所有的实体都用这个实现扩展了一个 AbstractEntity。
    【解决方案2】:

    吹一下,如果 A 和 B 共享相同的主键列,其中两个实体使用它们的主键连接,你应该改用@PrimaryKeyJoinColumn

    @Entity
    public class A implements Serializable {
    
        private MutableInt id = new MutableInt();
    
        private B b;
    
        public void setIdAsMutableInt(MutableInt id) {
            this.id = id;
        }
    
        @Id
        @GeneratedValue
        public Integer getId() {
            return id.intValue();
        }
    
        public void setId(Integer id) {
            this.id.setValue(id);
        }
    
        /**
          * Any ToOne annotation, such as @OneToOne and @ManyToOne, is EARGELY loaded, by default
          */
        @OneToOne(fetch=FetchType.LAZY)
        @PrimaryKeyJoinColumn
        @Cascade(CascadeType.SAVE_UPDATE)
        public B getB() {
            return b;
        }
    
        public void setB(B b) {
            b.setIdAsMutableInt(id);
    
            this.b = b;
        }
    
    }
    

    并且 B 注意你不需要 mappedBy 属性,因为 @PrimaryKeyJoinColumn

    @Entity
    public class B implements Serializable {
    
        private MutableInt id = new MutableInt();
    
        private A a;
    
        public void setIdAsMutableInt(MutableInt id) {
            this.id = id;
        }
    
        @Id
        public Integer getId() {
            return id.intValue();
        }
    
        public void setId(Integer id) {
            this.id.setValue(id);
        }
    
        @OneToOne(fetch=FetchType.LAZY)
        @PrimaryKeyJoinColumn
        public A getA() {
            return a;
        }
    
        public void setA(A a) {
            this.a = a;
        }
    
    }
    

    让我们测试(你可以测试,如果你愿意)

    A a = new A();
    B b = new B();
    
    a.setB(b);
    
    /**
      * b property will be saved because Cascade.SAVE_UPDATE
      */
    Serializable id = session.save(a);
    
    b = (B) session
            .createQuery("from B b left join fetch b.a where b.id = :id")
            .setParameter("id", id)
            .list()
            .get(0);
    
    Assert.assertEquals(b.getId(), b.getA().getId());
    

    请注意,我使用 MutableInt 字段(封装由 Integer 属性)而不是 Integer,因为 Integer 是不可变类型,A 和 B 共享相同的分配 id

    但如果 A 和 B 使用非主键连接,则应使用@JoinColumn 和 mappedBy(双向关系,对)如下

    @Entity
    public class A implements Serializable {
    
        private Integer id;
    
        private B b;
    
        @Id
        @GeneratedValue
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        /**
          * mappedBy="a" means: Look at "a" field / property at B Entity. If it has any assigned value, join us Through B_ID foreign key column
          */
        @OneToOne(fetch=FetchType.LAZY, mappedBy="a")
        /**
          * Table A has a foreign key column called "B_ID"
          */ 
        @JoinColumn(name="B_ID")
        @Cascade(CascadeType.SAVE_UPDATE)
        public B getB() {
            return b;
        }
    
        public void setB(B b) {
            this.b = b;
        }
    
    }   
    

    和B

    @Entity
    public class B implements Serializable {
    
        private Integer id;
    
        private A a;
    
        public void setIdAsMutableInt(MutableInt id) {
            this.id = id;
        }
    
        @Id
        @GeneratedValue
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        @OneToOne(fetch=FetchType.LAZY)
        public A getA() {
            return a;
        }
    
        public void setA(A a) {
            this.a = a;
        }
    
    }
    

    测试

    A a = new A();
    B b = new B();
    
    /**
      * Set up both sides
      * Or use some kind of add convenience method
      */ 
    a.setB(b);
    b.setA(a);
    
    /**
      * b property will be saved because Cascade.SAVE_UPDATE
      */
    Serializable id = session.save(a);
    
    b = (B) session
            .createQuery("from B b left join fetch b.a where b.id = :id")
            .setParameter("id", id)
            .list()
            .get(0);
    

    通过使用所有者方B,你会得到两个select语句这是因为B表不包含任何指向A表的外键列但是通过使用

    "从A a left join fetch a.b where a.id = :id"

    您将获得只有一个 select 语句,因为 A 知道如何使用 B_ID 外键列检索其连接的 B

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-10-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-07
      • 2011-06-15
      相关资源
      最近更新 更多