将项目从Ibatis转换为JPA 2.1时,我遇到了一个问题,我必须为一组对象加载一个完整的对象图,而不是为了性能原因而没有按N + 1选择或使用笛卡尔积。< / p>
用户查询将生成一个List&lt; Task&gt;,我需要确保在我返回任务时,他们已填充所有属性,包括父,子,依赖项和属性。首先让我解释一下所涉及的两个实体对象。
任务是层次结构的一部分。它可以有一个父任务,也可以有子。任务可以依赖于其他任务,由'dependencies'属性表示。任务可以包含许多属性,由属性属性表示。
尽可能简化了示例对象,并删除了样板代码。
@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;
}
给定任务的任务层次结构可以无限深,因此为了更容易获得整个图形,任务将通过“root”属性指向它的根任务。
在Ibatis中,我只是获取了根id的不同列表的所有任务,然后使用“task_id IN()”查询对所有属性和依赖项进行了即席查询。当我有这些时,我使用Java代码向所有模型对象添加属性,子项和依赖项,以便图形完整。对于任何大小的任务列表,我只会做3个SQL查询,而我正在尝试用JPA做同样的事情。由于'parent'属性指示添加子项的位置,因此我甚至不必查询这些。
我尝试了不同的方法,包括:
一种可能的解决方案可能是创建不受JPA管理的新Task对象,并使用这些对象将我的层次结构缝合在一起,我想我可以忍受它,但它感觉不到非常“JPA”,然后我无法使用JPA擅长 - 自动跟踪和持久更改我的对象。
任何提示都将不胜感激。如果有必要,我愿意使用供应商特定扩展。我使用Hibernate 4.3.5.Final在Wildfly 8.1.0.Final(Java EE7 Full Profile)中运行。
答案 0 :(得分:9)
有一些策略可以实现您的目标:
sub-select fetching会使用额外的子选择加载所有延迟实体,这是您第一次需要该给定类型的延迟关联时。这个声音起初很吸引人,但它会使你的应用程序易于获取额外的子选择实体的数量,并可能传播到其他服务方法。
batch fetching更容易控制,因为您可以在一个批次中强制执行要加载的实体数量,并且可能不会影响太多其他用例。
如果您的数据库支持,则使用recursive common table expression。
最后,它完全取决于您计划对所选行进行的操作。如果只是将它们显示在视图中,而不是a native query is more than enough。
如果您需要在多个请求中保留实体(首先是视图部分,第二个是更新部分),那么实体是更好的方法。
从您的回复中我发现您需要发出EntityManager.merge()
并且可能rely on cascading来传播子状态转换(添加/删除)。
由于我们正在讨论3个JPA查询,并且只要您没有获得笛卡尔积,那么您应该使用JPA。
您应该争取最少量的查询,但这并不意味着您将始终只需要一个查询。两个或三个查询根本不是问题。
只要您控制查询号码并且不进入N+1 query issue,您也可以使用多个查询。交换笛卡尔积(2个一对多的提取)进行一次加入和一次额外的选择是一个很好的交易。
最后你应该always check the EXPLAIN ANALYZE query plan并强化/重新思考你的策略。