一尘不染

使用JPA和Hibernate加载没有N + 1笛卡尔积的递归对象图

hibernate

将项目从Ibatis转换为JPA 2.1时,我面临一个问题,即我必须为一组对象加载完整的对象图,而不会由于性能原因而选择N + 1个选择或使用笛卡尔积。

用户查询将产生List ,并且我需要确保在返回任务时,它们具有填充的所有属性,包括 parentchildren
dependenciesproperties 。首先让我解释所涉及的两个实体对象。

任务是层次结构的一部分。它可以具有父任务,也可以具有子任务。一个任务可以依赖于其他任务,由’dependencies’属性表示。任务可以具有许多属性,这些
属性properties 属性表示。

示例对象已尽可能简化,并且删除了样板代码。

@Entity
public class Task {
    @Id
    private Long id;

    @ManyToOne(fetch = LAZY)
    private Task parent;

    @ManyToOne(fetch = LAZY)
    private Task root;

    @OneToMany(mappedBy = "task")
    private List<TaskProperty> properties;

    @ManyToMany
    @JoinTable(name = "task_dependency", inverseJoinColumns = { @JoinColumn(name = "depends_on")})
    private List<Task> dependencies;

    @OneToMany(mappedBy = "parent")
    private List<Task> children;
}

@Entity
public class TaskPropertyValue {
    @Id
    private Long id;

    @ManyToOne(fetch = LAZY)
    private Task task;

    private String name;
    private String value;
}

给定任务的Task层次结构可以无限深,因此,为了更轻松地获取整个图形,任务将通过’root’属性具有指向其根任务的指针。

在Ibatis中,我只是获取了所有任务以获取根ID的不同列表,然后使用“ task_id
IN()”查询对所有属性和依赖项进行了临时查询。当我拥有这些对象时,我使用Java代码为所有模型对象添加了属性,子代和依赖项,从而使图形完整。对于任何大小的任务列表,我将只执行3个SQL查询,而我尝试使用JPA进行同样的操作。由于’parent’属性指示要添加子项的位置,因此我什至不必查询那些子项。

我尝试了不同的方法,包括:

让延迟加载完成它的工作

  • 表现自杀,无需赘述:)

JOIN FETCH子代,JOIN FETCH依赖项,JOIN FETCH属性

  • 这是有问题的,因为生成的笛卡尔积很大,而且我的JPA实现(hibernate)不支持List,而仅在获取多个bag时才支持Set。一项任务可能具有大量属性,从而使笛卡尔积无效。

临时查询与我在ibatis中所做的相同

  • 我无法在Task对象的Lazy初始化集合中添加子项,依赖项和属性,因为Hibernate随后将尝试将它们添加为新对象。

一种可能的解决方案是创建不受JPA管理的新Task对象,并使用这些对象将我的层次结构缝合在一起,我想我可以接受,但是感觉不太“
JPA”,然后我无法使用JPA的优点是-自动跟踪和持久保存对我的对象的更改。

任何提示将不胜感激。如果需要,我愿意使用供应商特殊扩展。我在带有Hibernate 4.3.5.Final的Wildfly 8.1.0.Final(Java
EE7完整配置文件)中运行。


阅读 317

收藏
2020-06-20

共1个答案

一尘不染

可用选项

有一些策略可以实现您的目标:

  • 子选择获取将在第一次需要该给定类型的惰性关联时,为所有惰性实体加载附加的子选择。听起来一开始很吸引人,但它会使您的应用对要提取的其他子选择实体的数量脆弱,并且可能传播到其他服务方法。

  • 批量获取更容易控制,因为您可以强制在一个批次中加载实体的数量,并且可能不会影响太多其他用例。

  • 如果数据库支持,则使用递归公用表表达式

未雨绸缪

最后,所有这些都与您计划对所选行的处理有关。如果只是将它们显示在视图中,那么原生查询就足够了

如果您需要跨多个请求保留实体(第一个是视图部分,第二个是更新部分),则最好使用实体。

从您的答复中,我看到您需要发出,EntityManager.merge()并且可能需要级联来传播子状态转换(添加/删除)。

既然我们在谈论3个JPA查询,并且只要您没有得到笛卡尔积,就可以使用JPA。

结论

您应该争取最少的查询量,但这并不意味着您将永远只需要拥有一个查询。两个或三个查询根本不是问题。

只要您控制查询数量并且不涉及N + 1查询问题,您也可以使用多个查询。无论如何,将一个笛卡尔积(2个一对多取回)交易为一个连接和一个额外的选择。

最后,您应该始终检查EXPLAIN ANALYZE查询计划并加强/重新考虑您的策略。

2020-06-20