如果定义了双向关系,Hibernate 会执行两次相同的查询

Cla*_*sso 5 hibernate hibernate-mapping

我正在使用 Hibernate 4.3.8.Final 和 Oracle 11g 数据库。我在两个实体之间定义了一个非常简单的双向关系,命名为 Parent 和 Child,如下(省略了 getter 和 setter):

@Entity
public class Parent {

    @Id
    private Long id;

    @OneToOne(mappedBy="parent",fetch=FetchType.LAZY)
    private Child child;
}

@Entity
public class Child {

    @Id
    private Long id;

    @OneToOne(fetch=FetchType.EAGER)
    @JoinColumn(name="PARENT_ID")
    private Parent parent;
}
Run Code Online (Sandbox Code Playgroud)

生成对应表的SQL代码为:

CREATE TABLE PARENT
(
  ID  NUMBER(10)
);
CREATE UNIQUE INDEX PARENT_PK ON PARENT(ID);
ALTER TABLE PARENT ADD (
  CONSTRAINT PARENT_PK
  PRIMARY KEY
  (ID)
  USING INDEX PARENT_PK
  ENABLE VALIDATE);

--------------

CREATE TABLE CHILD
(
  ID         NUMBER(10),
  PARENT_ID  NUMBER(10)                         NOT NULL
);

CREATE UNIQUE INDEX CHILD_PK ON CHILD (ID);

CREATE UNIQUE INDEX CHILD_U01 ON CHILD (PARENT_ID);

ALTER TABLE CHILD ADD (
  CONSTRAINT CHILD_PK
  PRIMARY KEY
  (ID)
  USING INDEX CHILD_PK
  ENABLE VALIDATE,
  CONSTRAINT CHILD_U01
  UNIQUE (PARENT_ID)
  USING INDEX CHILD_U01
  ENABLE VALIDATE);

ALTER TABLE CHILD ADD (
  CONSTRAINT CHILD_R01 
  FOREIGN KEY (PARENT_ID) 
  REFERENCES PARENT (ID)
  ENABLE VALIDATE);
Run Code Online (Sandbox Code Playgroud)

结构非常简单:子项通过外键 ( PARENT_ID )链接到父项,该外键也是唯一的。使用以下代码从数据库中检索子实例:

entityManager.find(Child.class,1l);
Run Code Online (Sandbox Code Playgroud)

Hibernate 执行两个查询。似乎第一个用于加载关系的第一个方向(从孩子到父母),第二个用于加载另一个关系(从父母到孩子):

SELECT child0_.id AS id1_0_0_,
       child0_.PARENT_ID AS PARENT_ID2_0_0_,
       parent1_.id AS id1_1_1_
  FROM    Child child0_
       LEFT OUTER JOIN
          Parent parent1_
       ON child0_.PARENT_ID = parent1_.id
 WHERE child0_.id = ?;

SELECT child0_.id AS id1_0_1_,
       child0_.PARENT_ID AS PARENT_ID2_0_1_,
       parent1_.id AS id1_1_0_
  FROM    Child child0_
       LEFT OUTER JOIN
          Parent parent1_
       ON child0_.PARENT_ID = parent1_.id
 WHERE child0_.PARENT_ID = ?;
Run Code Online (Sandbox Code Playgroud)

Child 被配置为急切地加载 Parent,所以第一个查询是正确的:Child 和 Parent 表被连接和获取。我试图将 Parent 的“child”属性的 fetch 策略设置为 LAZY 以防止第二次查询,但它没有效果。

如何使用单个查询急切加载双向关系?事实上,如果检索到 N 个子实例,则执行 N+1 个查询。

小智 0

正如此处指出的,解决方案是使用非拥有方。如果您通过以下方式修改映射,将拥有方移动到父级(为简洁起见,受保护的字段包):

@Entity
public class Parent {
    @Id Long id;
    @OneToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="CHILD_ID")
    Child child;
}
Run Code Online (Sandbox Code Playgroud)

@Entity
public class Child {
    @Id Long id;
    @OneToOne(mappedBy="child",fetch=FetchType.EAGER)
    Parent parent;
}
Run Code Online (Sandbox Code Playgroud)

然后您可以查询生成单个选择的子项,因为有一个 FK 列:

@Test
public void shouldQueryTheDatabaseOnlyOnce() {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("stackoverflow");
    EntityManager entityManager = emf.createEntityManager();
    Child child = entityManager.find(Child.class, 2l);
    assertEquals((Long)1l, child.parent.id);
}
Run Code Online (Sandbox Code Playgroud)

结果是:

休眠:选择parent0_.id作为id1_1_0_,child1_.id作为id1_0_1_,child1_.PARENT_ID作为PARENT_I2_0_1_从父parent0_左外连接子child1_在parent0_.id=child1_.PARENT_ID,其中parent0_.id=?