实体之间关系的延迟加载是JPA中公认的最佳实践。 它的主要目标是仅从数据库中检索请求的实体,并仅在需要时加载相关实体。 如果我们只需要请求的实体,那是一个很好的方法。 但是,如果我们还需要一些相关实体,它会增加工作量,并可能导致性能问题。
让我们看一下触发初始化的不同方法及其特定的优点和缺点。
1.在映射关系上调用方法
让我们从最显而易见的方法开始,不幸的是也从效率最低的方法开始。 我们在EntityManager上使用find方法,并在关系上调用一个方法。
Order order = this.em.find(Order.class, orderId);
order.getItems().size();
此代码工作得很好,易于阅读并且经常使用。 那么,这是什么问题呢?
好吧,您可能知道。 此代码执行附加查询以初始化关系。 这听起来不像是一个真正的问题,但是可以让我们计算出一个更加真实的场景中执行的查询的数量。
假设我们有一个具有5个关系的实体,需要初始化。 因此,我们将获得1 + 5 = 6个查询 。 好的,那是5个附加查询。 这似乎仍然不是一个大问题。
但是我们的应用程序将被多个用户并行使用(我希望)。 假设我们的系统必须为100个并行用户提供服务器。 然后,我们将获得100 + 5 * 100 = 600个查询 。
好的,很明显,这种方法提供了可行的解决方案,但不是一个好的解决方案。 或早或晚,额外执行的查询数量将使我们的应用程序变慢。 因此,我们应该尝试避免这种方法,并看看其他一些选择。
2.在JPQL中获取Join
初始化惰性关系的一个更好的选择是将JPQL查询与获取联接一起使用。
Query q = this.em.createQuery("SELECT o FROM Order o JOIN FETCH o.items i WHERE o.id = :id");
q.setParameter("id", orderId);
newOrder = (Order) q.getSingleResult();
这告诉实体管理器在同一查询中获取选定的实体和关系。 这种方法的优缺点很明显:
优点是所有内容都在一个查询中获取。 从性能的角度来看,这比第一种方法要好得多。
主要缺点是我们需要编写其他代码来执行查询。 但是,如果实体具有多个关系,并且我们需要针对不同的用例初始化不同的关系,那就更糟了。 在这种情况下,我们需要为获取联接关系的每个所需组合编写查询。 这会变得很混乱。
在JPQL语句中使用提取联接可能需要大量查询,这将使维护代码库变得困难。 因此,在开始编写大量查询之前,我们应该考虑可能需要的不同访存联接组合的数量。 如果数量很少,那么这是一种限制执行的查询数量的好方法。
3.获取条件API中的加入
好的,这种方法与以前的方法基本相同。 但是这次我们使用的是Criteria API,而不是JPQL查询。
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery q = cb.createQuery(Order.class);
Root o = q.from(Order.class);
o.fetch("items", JoinType.INNER);
q.select(o);
q.where(cb.equal(o.get("id"), orderId));Order order = (Order)this.em.createQuery(q).getSingleResult();
优点和缺点与带有访存连接的JPQL查询相同。 使用一个查询从数据库中检索实体和关系,我们需要每种关系组合的特定代码。 但是,如果我们使用的是Criteria API,我们通常已经有很多用例特定的查询代码。 因此,这可能不是一个大问题。
如果我们已经在使用Criteria API来构建查询,那么这是减少执行查询数量的好方法。
4.命名实体图
命名实体图是JPA 2.1的新功能。 它可用于定义应从数据库中查询的实体图。 实体图的定义是通过注释完成的,并且与查询无关。
如果您不熟悉此功能,则可以查看我以前的一篇博客文章 ,其中对它进行了更详细的介绍。
@Entity
@NamedEntityGraph(name = "graph.Order.items", attributeNodes = @NamedAttributeNode("items"))
public class Order implements Serializable {
....
然后,EntityManager的find方法可以使用命名的实体图。
EntityGraph graph = this.em.getEntityGraph("graph.Order.items");Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);Order order = this.em.find(Order.class, orderId, hints);
这基本上是我们第一种方法的改进版本。 实体管理器将通过一个查询从数据库检索定义的实体图。 唯一的缺点是,我们需要为将在一个查询中检索到的每种关系组合注释一个命名实体图。 与第二种方法一样,我们将需要更少的附加注释,但是它仍然会变得非常混乱。
因此,如果我们只需要定义有限数量的实体图并将其重用于不同的用例,则命名实体图是一个很好的解决方案。 否则,代码将变得难以维护。
5.动态实体图
动态实体图类似于命名实体图,并且在以前的一篇文章中也进行了解释。 唯一的区别是,实体图是通过Java API定义的。
EntityGraph graph = this.em.createEntityGraph(Order.class);
Subgraph itemGraph = graph.addSubgraph("items");Map hints = new HashMap();
hints.put("javax.persistence.loadgraph", graph);Order order = this.em.find(Order.class, orderId, hints);
通过API进行定义既可以是优点,也可以是缺点。 如果我们需要大量用例特定的实体图,则最好在特定的Java代码中定义实体图,并且不向该实体添加附加注释。 这样可以避免带有数十个注释的实体。 另一方面,动态实体图需要更多代码和其他方法才能重用。
因此,我建议使用动态实体图,如果我们需要定义用例特定的图,则将不会重复使用该图。 如果我们想重用实体图,则更容易注释命名的实体图。
结论
我们研究了5种不同的初始化惰性关系的方法。 正如我们所看到的,它们每个都有其优点和缺点。 那么从这篇文章中要记住什么呢?
- 通过在映射关系上调用方法来初始化惰性关系会导致附加查询。 出于性能原因,应避免这种情况。
- JPQL语句中的访存联接将查询数量减少到一个,但是我们可能需要很多不同的查询。
- Criteria API还支持提取连接,对于每种需要初始化的关系,我们都需要特定的代码。
- 如果我们将在代码中重用已定义的图,则命名实体图是一个很好的解决方案。
- 如果我们需要定义特定于用例的图,则动态实体图可能是更好的解决方案。
翻译自: https://www.javacodegeeks.com/2014/12/5-ways-to-initialize-lazy-relations-and-when-to-use-them.html