如何在 JPQL 中进行“深度”获取连接?
Posted
技术标签:
【中文标题】如何在 JPQL 中进行“深度”获取连接?【英文标题】:How do I do a "deep" fetch join in JPQL? 【发布时间】:2013-05-16 20:20:00 【问题描述】:我认为我永远不会完全理解 fetch joins。
我有一个查询,我正试图急切地将引用“膨胀”到两个级别。
也就是说,我的A
有一个可选的Collection
B
s,每个B
有0 或1 个C
。众所周知,B
集合的大小很小(10-20 个顶部)。我想预取这张图。
A
的B
关系标记为FetchType.LAZY
并且是可选的。 B
与 C
的关系也是可选的,FetchType.LAZY
。
我希望我能做到:
SELECT a
FROM A a
LEFT JOIN FETCH a.bs // look, no alias; JPQL forbids it
LEFT JOIN a.bs b // "repeated" join necessary since you can't alias fetch joins
LEFT JOIN FETCH b.c // this doesn't seem to do anything
WHERE a.id = :id
当我运行它时,我看到确实提取了 A
s B
集合(我在 SQL 中看到 LEFT JOIN
引用了 B
映射到的表)。
但是,我没有看到C
的表已被提取的此类证据。
如何从给定的A
预取所有“可访问”的C
s 和所有B
s 和所有C
s?我看不出有什么办法。
【问题讨论】:
您使用什么 JPA 提供程序? EclipseLink 中有专门针对此类功能的提示。查看eclipselink.join-fetch
和eclipselink.batch
提示...
谢谢。如果我使用eclipselink.join-fetch
,看起来我只能设置一个属性。那是对的吗?例如,如果我将我的join-fetch
大写于a.bs.c
,那么如果我还想加入 fetch——比如说——a.bs.d
,那我就不走运了。对吗?
哦,这很有趣。 EclipseLink 文档 (wiki.eclipse.org/EclipseLink/UserGuide/JPA/…) 并没有说重复的查询提示键是可能的,但也许它们是?看到这个有趣的链接:github.com/mysema/querydsl/issues/348
我可以验证多个eclipselink.join-fetch
提示是否有效。我们在工作中大量使用它,生成的 SQL 语句是正确的。
谢谢。你有没有遇到过这个错误:bugs.eclipse.org/bugs/show_bug.cgi?id=408719?
【参考方案1】:
JPA 规范不允许为 fetch 连接设置别名,但一些 JPA 提供程序允许。
EclipseLink 从 2.4 开始。 EclipseLink 还允许使用点表示法进行嵌套连接获取(即“JOIN FETCH a.bs.c”),并支持允许嵌套连接的查询提示“eclipselink.join-fetch”(您可以指定多个相同提示名称的提示)。
一般来说,在 fetch 连接上使用别名时需要小心,因为这会影响返回的数据。
看, http://java-persistence-performance.blogspot.com/2012/04/objects-vs-data-and-filtering-join.html
【讨论】:
谢谢,@James;由于各种原因,我们需要保持我们的 JPQL 标准,所以它看起来像(字符串格式)查询提示。值得注意的是,使用其中一些会导致错误:bugs.eclipse.org/bugs/show_bug.cgi?id=408719(其他人在 2010 年在这里也报告过:forums.oracle.com/forums/thread.jspa?threadID=847970。无论如何,我对预取太深持怀疑态度,所以也许这奇怪的是最好的。: -) eclipselink.join-fetch 也在 eclipseLink 2.3 中工作:.setHint("eclipselink.join-fetch", "a.bs.c")【参考方案2】:我正在使用 Hibernate(这可能是特定于它的)并且我已经取得了成功:
SELECT DISTINCT a, b
FROM A a
LEFT JOIN a.bs b
LEFT JOIN FETCH a.bs
LEFT JOIN FETCH b.c
WHERE a.id = :id
(注意选择列表中的b
)。
这是我发现这对我有用的唯一方法,请注意,这会为我返回 Object[]
,然后我在代码中过滤它,如下所示:
(List<A>) q.getResultList().stream().map(pair -> (A) (((Object[])pair)[0])).distinct().collect(Collectors.toList());
【讨论】:
【参考方案3】:JPA 不允许嵌套连接提取,也不允许连接上的别名 fetch,所以这可能是特定于 JPA 提供程序的。
在 EclipseLink 中,您可以指定查询提示来执行嵌套连接 获取。
你不能在 JPQL 中让它递归,你最多只能去 n 个级别。在 EclipseLink 中,您可以使用 @JoinFetch 或 @BatchFetch 使查询递归的映射。
看, http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html
来源:http://www.coderanch.com/t/570828/ORM/databases/Recursive-fetch-join-recursively-fetching
【讨论】:
【参考方案4】:不完全是 JPQL,但您可以在纯 JPA 中使用 Criteria 查询来实现:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<MyEntity> q = cb.createQuery(MyEntity.class);
Root<MyEntity> root = q.from(MyEntity.class);
q.select(root);
Fetch bsFetch = root.fetch("b", JoinType.LEFT); //fetch b, property of MyEntity and hold fetch object
bsFetch.fetch("c", JoinType.LEFT); //fetch c, property of b
对这种嵌套获取的支持是特定于供应商的(因为 JPA 不要求他们这样做),但 eclipselink 和 hibernate 都支持它,这样您的代码就保持与供应商无关。
【讨论】:
以上是关于如何在 JPQL 中进行“深度”获取连接?的主要内容,如果未能解决你的问题,请参考以下文章