为什么 hibernate 执行两个查询来急切加载 @OneToOne 双向关联?

发布于 2024-09-12 04:25:49 字数 856 浏览 1 评论 0原文

我有一个实体 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? 我认为这个查询加载了 B 中的 A 内容,但是这个 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 需要两次查询...为什么?

i have entity A that has-a B entity, and B has-a A with @OneToOne bidirectional association.

Now, when i findall A records, hibernate perform two queries with a left outer join on B, something like this:

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=?

First query load A and B fields and it is ok, but why perform second query to reload A?
I think this query load the A content in B, but this A is obviusly the A that contains B... so its already loaded with first query, isn't true?

-- EDIT --

Entity A:

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

Entity B:

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

This is the situation, and a findAll on A need two queries... why?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

纸短情长 2024-09-19 04:25:49

打击,如果 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,你将得到两个选择语句它发生是因为B表确实不包含任何指向表 A 的外键列但是通过使用

“从 A 左连接获取 ab,其中 a.id = :id”

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

Blow, if A and B share The same primary key column where both entities are joined by using their primary key, you should use @PrimaryKeyJoinColumn instead

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

}

And B Notice you do not need mappedBy attribute because of @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;
    }

}

Let's Test (You can Test if you want)

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());

Notice I use a MutableInt field (encapsulated by a Integer property) instead of Integer because Integer is a immutable Type as a way Both A and B share The SAME assigned id

But if A and B are joined by using other Than their primary key, you should use @JoinColumn and mappedBy (bi-directional relationship, right) as follows

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

}   

And 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;
    }

}

To test

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);

By using The owner side B, you will get Two select statements It occurs because B Table does not contain any foreign key column which points To Table A But by using

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

You will get just one select statement because A knows how To retrieve its joined B by using its B_ID foreign key column

小嗷兮 2024-09-19 04:25:49

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

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

听起来您正在尝试对双向一对一映射进行建模 - 看看 手册中有关此内容的部分 查看实现它的推荐方法。

What does your mapping look like exactly?

Do your A and B classes correctly implement hashCode() and equals() so that Hibernate can tell that the A instance pointed to by B is the same instance of the first A?

Sounds like you are trying to model a bi-directional one-to-one mapping - take a look at the section in the manual on this to see the recommended methods for accomplishing it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文