加载递归对象图,不带 N+1 笛卡尔积,带 JPA 和休眠
在将项目从 Ibatis 转换为 JPA 2.1 时,我遇到了一个问题,即我必须为一组对象加载一个完整的对象图,而无需命中 N+1 选择或出于性能原因使用笛卡尔积。
用户查询将生成 List<Task>,我需要确保在返回任务时,它们已填充所有属性,包括父项、子项、依赖项和属性。首先让我解释一下所涉及的两个实体对象。
任务是层次结构的一部分。它可以有一个父任务,也可以有一个子任务。任务可以依赖于其他任务,由“依赖关系”属性表示。一个任务可以有许多属性,由属性属性表示。
示例对象已尽可能简化,并删除了样板代码。
@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 层次结构可以无限深,因此为了更轻松地获取整个图形,Task 将通过“root”属性具有指向其根任务的指针。
在 Ibatis 中,我只需获取根 ID 的不同列表的所有 Tasks,然后使用“task_id IN ()”查询对所有属性和依赖项进行即席查询。当我拥有这些时,我使用Java代码向所有模型对象添加属性,子级和依赖项,以便图形完整。对于任何大小的任务列表,我只会做3个SQL查询,我正在尝试对JPA做同样的事情。由于“parent”属性指示在何处添加子级,因此我甚至不必查询这些子级。
我尝试了不同的方法,包括:
让延迟加载完成它的工作
- 表演自杀,无需詳細:)
加入 FETCH 子级,加入 FETCH 依赖项,加入 FETCH 属性
- 这是有问题的,因为生成的笛卡尔积是巨大的,而我的JPA实现(Hibernate)不支持List,只在获取多个袋子时支持Set。一个任务可以有大量的属性,使笛卡尔积无效。
临时查询,就像我在 ibatis 中所做的那样
- 我无法将子项,依赖项和属性添加到任务对象上的懒惰初始化集合中,因为Hibernate将尝试将它们添加为新对象。
一种可能的解决方案是创建不由JPA管理的新任务对象,并使用这些对象将我的层次结构缝合在一起,我想我可以接受这一点,但它感觉不是很“JPA”,然后我不能使用JPA来做它擅长的事情 - 自动跟踪和持久保存对我的对象的更改。
任何提示将不胜感激。如有必要,我愿意使用供应商专用扩展。我在Wildfly 8.1.0.Final(Java EE7完整配置文件)中运行,Hibernate 4.3.5.Final。