使用Hibernate JPA获取多个一对多关系

8
我正在使用Hibernate JPA 1.0。 我有这种类型的模型,并且我认为多对一和一对一关系是“急切地”获取,而一对多关系是“懒惰地”获取。
我想要获取实体A及其所有关联,其中a.id =?
是否可以在单个查询中加载此实体? 或在子查询中加载,考虑“n + 1选择问题”!
到目前为止,我加载所有A关联的解决方案是执行以下操作:
"Select DISTINCT a from A a JOIN FETCH a.bs WHERE a.id=:aID"
然后使用代码迭代以获取所有其他关联。
Collection B bs = A.getBs();
         for (final B b : bs) {
         b.getCs().getDs().size();
         b.getEs().getDs().size();
         b.getFs().getDs().size();
         }

显然,有更好的方法来做这件事。

给Dimitri,我有同样的问题。您还没有接受任何答案。那么哪个解决方案适合您? - Manan Shah
我通常使用批量获取来解决这类问题。 - Dragan Bozanovic
3个回答

8
你可能会对阅读这篇文章感兴趣:这里。我已经测试过了。
@Fetch(FetchMode.SUBSELECT)

同时使用集合(Set)代替列表(List),并且结合 eager 加载方式,这样做可以使其正常工作。


在关系上使用FetchType.EAGER通常是一种反模式,因为并不是所有的关系都需要被加载,没有使用它们就加载它们是JPA相关应用程序中性能问题的一个很好的例子。通常做法是先加载数据,然后通过您的JPQL查询或通过initialize函数独立地初始化实际需要的数据。 - user5772563

3
使用FETCH JOIN。JPA 1.0规范中的说明如下:

4.4.5.3 Fetch Joins

A FETCH JOIN enables the fetching of an association as a side effect of the execution of a query. A FETCH JOIN is specified over an entity and its related entities.

The syntax for a fetch join is

fetch_join ::= [ LEFT [OUTER] | INNER ] JOIN FETCH join_association_path_expression

The association referenced by the right side of the FETCH JOIN clause must be an association that belongs to an entity that is returned as a result of the query. It is not permitted to specify an identification variable for the entities referenced by the right side of the FETCH JOIN clause, and hence references to the implicitly fetched entities cannot appear elsewhere in the query.

The following query returns a set of departments. As a side effect, the associated employees for those departments are also retrieved, even though they are not part of the explicit query result. The persistent fields or properties of the employees that are eagerly fetched are fully initialized. The initialization of the relationship properties of the employees that are retrieved is determined by the metadata for the Employee entity class.

SELECT d
FROM Department d LEFT JOIN FETCH d.employees
WHERE d.deptno = 1

A fetch join has the same join semantics as the corresponding inner or outer join, except that the related objects specified on the right-hand side of the join operation are not returned in the query result or otherwise referenced in the query. Hence, for example, if department 1 has five employees, the above query returns five references to the department 1 entity.

当然,要明智使用,不要连接太多的表格,否则会影响性能。

是的,但需要执行以下操作: 从Department d左连接FETCH d.employees es,再左连接FETCH es.employee.addresses WHERE d.deptno = 1并加载每个员工拥有的所有地址。 - user390517
1
@Dimitri:FETCH JOIN 是一种急切获取关联的方式。我不明白你的意思(而且我也不确定你是否想要一个 LEFT JOIN)。 - Pascal Thivent

1

寻找可信和/或官方来源的答案。

JBoss ORM文档怎么样?

https://docs.jboss.org/hibernate/orm/current/userguide/html_single/chapters/fetching/Fetching.html


有一些定义数据获取的范围:
静态
在映射中静态定义获取策略。在没有动态定义策略的情况下,使用静态定义的获取策略。
SELECT
执行单独的SQL查询来加载数据。这可以是EAGER(第二个查询立即发出)或LAZY(第二个查询延迟到需要数据时再发出)。这通常被称为N+1策略。
JOIN
本质上是EAGER样式的获取。通过使用SQL外连接来获取要获取的数据。
BATCH
执行单独的SQL查询来加载一些相关数据项,使用批处理大小作为SQL WHERE子句的IN限制的一部分。同样,这可以是EAGER(第二个查询立即发出)或LAZY(第二个查询延迟到需要数据时再发出)。
SUBSELECT
执行单独的SQL查询来基于用于加载所有者的SQL限制加载关联数据。同样,这可以是EAGER(第二个查询立即发出)或LAZY(第二个查询延迟到需要数据时再发出)。
动态
动态定义实际上是针对用例的。有多种方式定义动态获取:
在映射中定义获取配置文件,但可以在会话中启用/禁用。
HQL/JPQL和Hibernate和JPA标准查询都有指定特定查询的获取能力。
实体图形从Hibernate 4.2(JPA 2.1)开始也是一种选择。

为了证明上面的答案,这里提供一个例子:

FetchMode.SUBSELECT 为了演示FetchMode.SUBSELECT的工作原理,我们将修改FetchMode.SELECT映射示例以使用FetchMode.SUBSELECT:

示例17. FetchMode.SUBSELECT 映射示例:

@OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
@Fetch(FetchMode.SUBSELECT)
private List<Employee> employees = new ArrayList<>();

现在,我们将获取所有符合给定过滤条件的Department实体,然后浏览它们的员工集合。Hibernate将通过生成单个SQL语句来初始化先前获取的所有Department实体的所有员工集合来避免N+1查询问题。而不是使用所有实体标识符,Hibernate只需重新运行先前获取Department实体的查询。示例18. FetchMode.SUBSELECT映射示例:
List<Department> departments = entityManager.createQuery(
    "select d " +
    "from Department d " +
    "where d.name like :token", Department.class)
    .setParameter( "token", "Department%" )
    .getResultList();

log.infof( "Fetched %d Departments", departments.size());

for (Department department : departments ) {
    assertEquals(3, department.getEmployees().size());
}

-- 获取了2个部门
SELECT
    d.id as id1_0_
FROM
    Department d
where
    d.name like 'Department%'

-- Fetched 2 Departments

SELECT
    e.department_id as departme3_1_1_,
    e.id as id1_1_1_,
    e.id as id1_1_0_,
    e.department_id as departme3_1_0_,
    e.username as username2_1_0_
FROM
    Employee e
WHERE
    e.department_id in (
        SELECT
            fetchmodes0_.id
        FROM
            Department fetchmodes0_
        WHERE
            d.name like 'Department%'
    )

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接