休眠事实:访存策略的重要性

在使用ORM工具时,每个人都承认数据库设计和实体到表映射的重要性。 这些方面引起了很多关注,而诸如获取策略之类的事情可能只是推迟了。

我认为,不应将实体获取策略与实体映射设计分开,因为除非经过适当设计,否则它可能会影响整体应用程序性能。

在Hibernate和JPA如此流行之前,设计每个查询需要付出大量的努力,因为您必须明确选择要从中选择的所有联接以及所有您感兴趣的列。足够,DBA可以优化运行缓慢的查询。

在JPA时代,JPA-QL或HQL查询正在获取实体及其一些关联关系。 这使开发变得容易,因为它使我们免于手动选择我们感兴趣的所有表字段的麻烦,有时会自动生成联接或其他查询来满足我们的需求。

这是一把双刃剑。 一方面,您可以更快地交付功能,但如果自动生成的SQL查询效率不高,则应用程序的整体性能可能会受到严重影响。

那么,实体获取策略是什么?

当JPA加载实体时,它也会同时加载所有EAGER或“ join fetch”关联。 只要打开持久性上下文,在LAZY关联中导航也将通过其他已执行的查询来获取这些关联。

默认情况下,将更容易地自动获取JPA @ManyToOne和@OneToOne批注,而将@OneToMany和@ManyToMany关系视为LAZY。 这是默认策略,并且Hibernate不会神奇地优化对象检索,它只会执行所指示的操作。

尽管小型项目不需要全面的实体获取计划,但中大型应用程序永远都不应忽略它。

从一开始就计划获取策略,并在整个开发周期中进行调整,这并不是“过早的优化”,而这只是任何ORM设计的自然组成部分。

默认的获取策略是您通过JPA映射定义的策略,而手动连接获取则是在使用JPA-QL查询时定义的策略。

我能给您的最佳建议是赞成手动获取策略(在使用获取操作符的JPA-QL查询中定义)。 尽管一定要急切地获取某些@ManyToOne或@OneToOne关联,但在大多数情况下,并不是每个获取操作都需要它们。

对于儿童协会,将它们标记为LAZY并仅在需要时才“联接提取”它们总是比较安全的,因为它们可以轻松地生成带有不需要联接的大型SQL结果集。

将大多数关联定义为LAZY要求我们使用“ join fetch” JPA-QL运算符,并且仅检索满足给定请求所需的关联。 如果您忘记正确地“联接获取”,则在浏览惰性关联时,持久性上下文将代表您运行查询,这可能会产生“ N + 1”问题,或者可能是通过简单联接检索到的其他SQL查询首先。

作为一个具体的例子,让我们从下图开始:

休眠事实:访存策略的重要性

产品实体关联映射为:

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "company_id", nullable = false)
private Company company;

@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "product", optional = false)
private WarehouseProductInfo warehouseProductInfo;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "importer_id")
private Importer importer;

@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "product", orphanRemoval = true)
@OrderBy("index")
private Set<Image> images = new LinkedHashSet<Image>();

大多数关联都标记为LAZY,因为每次加载产品时都不需要获取所有关联。 仅在显示库存信息时才需要仓库。 导入程序仅在某些显示中使用,我们将在必要时提取它。 图像是惰性的,因为并非所有视图都需要显示这些图像。

因为我们所有的观点都需要它,所以只有公司热切地被获取,并且在我们的应用程序中,始终必须在给定公司的背景下考虑产品。

即使@ManyToOne默认使用EAGER fetch选项,最好还是显式地设置默认的获取策略(这会使代码更具自描述性)。

用例1:通过id加载产品将生成以下SQL

SELECT product0_.id          AS id1_7_1_,
       product0_.code        AS code2_7_1_,
       product0_.company_id  AS company_4_7_1_,
       product0_.importer_id AS importer5_7_1_,
       product0_.name        AS name3_7_1_,
       company1_.id          AS id1_1_0_,
       company1_.name        AS name2_1_0_
FROM   product product0_
INNER JOIN company company1_ ON product0_.company_id = company1_.id
WHERE  product0_.id = ?

每次我们通过实体管理器加载时,都会使用默认的获取策略,这意味着公司将与我们选择的产品一起获取。

用例2:通过JPA-QL查询选择产品(绕过持久性上下文第一级缓存)

entityManager.createQuery(
   "select p " +
   "from Product p " +
   "where p.id = :productId", Product.class)
.setParameter("productId", productId)
.getSingleResult();

这将执行以下SQL查询:

SELECT product0_.id          AS id1_7_,
       product0_.code        AS code2_7_,
       product0_.company_id  AS company_4_7_,
       product0_.importer_id AS importer5_7_,
       product0_.name        AS name3_7_
FROM   product product0_
WHERE  product0_.id = ?

因此,使用JPA-QL会覆盖默认的获取策略,但是如果我们要浏览惰性关联,它仍然使我们容易受到攻击。 如果Persistence Context关闭,则在访问惰性关系时会出现LazyInitializationException,但如果未关闭,则会生成其他选择查询,这可能会影响应用程序性能。

用例3:选择具有相关仓库和进口商关联的产品列表:

entityManager.createQuery(
   "select p " +
   "from Product p " +
   "inner join fetch p.warehouseProductInfo " +
   "inner join fetch p.importer", Product.class)
.getResultList();

这将生成以下SQL:

SELECT product0_.id          AS id1_7_0_,
       warehousep1_.id       AS id1_11_1_,
       importer2_.id         AS id1_3_2_,
       product0_.code        AS code2_7_0_,
       product0_.company_id  AS company_4_7_0_,
       product0_.importer_id AS importer5_7_0_,
       product0_.name        AS name3_7_0_,
       warehousep1_.quantity AS quantity2_11_1_,
       importer2_.name       AS name2_3_2_
FROM   product product0_
INNER JOIN warehouseproductinfo warehousep1_ ON product0_.id = warehousep1_.id
INNER JOIN importer importer2_ ON product0_.importer_id = importer2_.id

在这里,您可以看到JPA-QL显式提取策略将覆盖默认策略。 因为我们尚未指定与Company的“ join fetch”,所以将忽略EAGER关联。

用例4:在显式加入获取产品的同时选择一系列图像,即使所选实体不是我们要覆盖其策略的实体,也将覆盖默认策略:

entityManager.createQuery(
   "select i " +
   "from Image i " +
   "inner join fetch i.product p " +
   "where p.id = :productId", Image.class)
.setParameter("productId", productId)
.getResultList();

这将生成以下SQL:

SELECT image0_.id            AS id1_2_0_,
       product1_.id          AS id1_7_1_,
       image0_.index         AS index2_2_0_,
       image0_.name          AS name3_2_0_,
       image0_.product_id    AS product_4_2_0_,
       product1_.code        AS code2_7_1_,
       product1_.company_id  AS company_4_7_1_,
       product1_.importer_id AS importer5_7_1_,
       product1_.name        AS name3_7_1_
FROM   image image0_
INNER JOIN product product1_ ON image0_.product_id = product1_.id
WHERE  product1_.id = ?

我还需要添加一件事,这与WarehouseProductInfo的@oneToOne关系有关。 对于可选的@OnetoOne关联,将忽略LAZY属性,因为Hiberante必须知道它是否必须使用null或代理来填充您的Entity。 在我们的示例中,将其强制性是有意义的,因为无论如何每种产品都位于仓库中。 在其他情况下,您可以简单地使关联成为单向的,并仅保留控制链接的部分(外键所在的部分)。

翻译自: https://www.javacodegeeks.com/2013/11/hibernate-facts-the-importance-of-fetch-strategy.html