将项目从Ibatis转换为JPA 2.1时,我面临一个问题,即我必须为一组对象加载完整的对象图,而不会由于性能原因而选择N + 1个选择或使用笛卡尔积。
用户查询将产生List ,并且我需要确保在返回任务时,它们具有填充的所有属性,包括 parent , children , dependencies 和 properties 。首先让我解释所涉及的两个实体对象。
任务是层次结构的一部分。它可以具有父任务,也可以具有子任务。一个任务可以依赖于其他任务,由’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’属性指示要添加子项的位置,因此我什至不必查询那些子项。
我尝试了不同的方法,包括:
一种可能的解决方案是创建不受JPA管理的新Task对象,并使用这些对象将我的层次结构缝合在一起,我想我可以接受,但是感觉不太“ JPA”,然后我无法使用JPA的优点是-自动跟踪和持久保存对我的对象的更改。
任何提示将不胜感激。如果需要,我愿意使用供应商特殊扩展。我在带有Hibernate 4.3.5.Final的Wildfly 8.1.0.Final(Java EE7完整配置文件)中运行。
有一些策略可以实现您的目标:
子选择获取将在第一次需要该给定类型的惰性关联时,为所有惰性实体加载附加的子选择。听起来一开始很吸引人,但它会使您的应用对要提取的其他子选择实体的数量脆弱,并且可能传播到其他服务方法。
批量获取更容易控制,因为您可以强制在一个批次中加载实体的数量,并且可能不会影响太多其他用例。
如果数据库支持,则使用递归公用表表达式。
最后,所有这些都与您计划对所选行的处理有关。如果只是将它们显示在视图中,那么原生查询就足够了。
如果您需要跨多个请求保留实体(第一个是视图部分,第二个是更新部分),则最好使用实体。
从您的答复中,我看到您需要发出,EntityManager.merge()并且可能需要级联来传播子状态转换(添加/删除)。
EntityManager.merge()
既然我们在谈论3个JPA查询,并且只要您没有得到笛卡尔积,就可以使用JPA。
您应该争取最少的查询量,但这并不意味着您将永远只需要拥有一个查询。两个或三个查询根本不是问题。
只要您控制查询数量并且不涉及N + 1查询问题,您也可以使用多个查询。无论如何,将一个笛卡尔积(2个一对多取回)交易为一个连接和一个额外的选择。
最后,您应该始终检查EXPLAIN ANALYZE查询计划并加强/重新考虑您的策略。