我们在Oracle中有这四个持久化树状结构的表,并且我们正在使用Hibernate来处理持久化这项肮脏的工作。它在几乎所有场景中都工作得很好,除了查询一组这样的结构以将其作为JSON传输之外。检索3k个实体大约需要30秒,所以我尝试进一步调查。
实体如下所示(为简洁起见,省略了许多代码):
@Table(name = "ROOT")
public class Root {
@Id
private Long id;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "root")
@Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private List<Stem> stems;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "root")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private List<Branch> branches;
}
@Table(name = "STEM")
public class Stem {
@Id
private Long id;
@ManyToOne
@JoinColumn(name = "ROOT_ID")
private Root root;
}
@Table(name = "BRANCH")
public class Branch {
@Id
private Long id;
@ManyToOne
@JoinColumn(name = "ROOT_ID")
private Root root;
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "branch")
private List<Leaf> leaves;
}
@Table(name = "LEAF")
public class Leaf {
@Id
private Long id;
@ManyToOne
@JoinColumn(name = "BRANCH_ID")
private Branch branch;
}
这些测试是在25个e1d0d1、略低于200个e1d1d1和略多于3k个e1d2d1的情况下进行的。此外,还有2500个e1d3d1出席。
阅读日志,对我来说,Hibernate会产生DFS,这会导致大量的查询(显然,每个实体都有一个)被触发。
因此,我尝试通过为四个表中的每个表触发一个查询来实现一个BFS(为简洁起见,在Python中)。令人惊讶的是,我能够在不到半秒的时间内准备好相同的JSON(包括json.dump
的全部时间)!脚本是这样的(所有关系都是这样重复的):
cur.execute('select * from PARENT_TABLE')
parents = parent_tuples_to_dict(cur.fetchmany(25))
cur.execute(f'select * from CHILD_TABLE where PARENT_ID in ({",".join([p[id] for p in parents])})')
children = child_tuples_to_dict(cur.fetchall())
for child in children.values():
parent = next(p for p in parents if p['id'] == child['parent_id'])
parent['children'].append(child)
我正在考虑只更改DAO来执行BFS,使用@SqlResultSetMapping
和原生查询,但这看起来是一个相当难看的解决方案。此外,我认为我需要自己管理实体以防止内存泄漏。
我的问题是:
**问题1:**会不会是我们做错了什么导致这种表现的原因(我试图恢复关于实体的所有相关细节,但如果需要,我可以带来更多细节)?
**问题#2:**使用JPA/Hibernate/命名查询等进行这种查询的正确/好方法是什么?
**问题#3:**如果“手动”处理真的成为最有效的解决方案,我是否真的需要“手动”管理实体,以防止内存泄漏?
1条答案
按热度按时间zu0ti5jz1#
我的解决方案
所以..。我在Facebook小组的一些同事的帮助下发现,这是一种称为N+1查询的问题,在使用ORM时,这是一种常见的问题。
我的解决方案是使用本文中描述的关于multiple bag fetch exception的技巧来利用持久化上下文行为,并获得与我使用Python脚本获得的结果类似的结果。
我是如何解决它的
基本上,一旦我加载了父表(将子表标记为惰性),我就会在对要加载的子表执行连接提取时,再次选择那些项。
对于我在问题中发布的示例,解决方案如下所示:
尝试回答我留下的问题
问题#1:是的。我们忽略了N+1个查询方案。
问题#2:看起来使用Hibernate解决N+1查询的正确方法之一是使用
Hibernate::initialize
和@Fetch(FetchMode.SUBSELECT)
,但我无法找到如何解决。问题#3:我没有时间找出它,我有一种强烈的感觉,只使用
@SqlResultSetMapping
就可以充分利用持久性上下文。如果我曾经尝试过,我会用结果编辑这个答案。