介绍
休眠获取策略确实可以使几乎没有爬网的应用程序和响应速度很快的应用程序有所不同。 在这篇文章中,我将解释为什么您应该选择基于查询的获取而不是全局获取计划。
取得101
Hibernate定义了四种关联检索策略 :
提取策略 | 描述 |
---|---|
加入 | 原始SELECT语句中的关联是OUTER JOINED |
选择 | 附加的SELECT语句用于检索关联的实体(实体) |
子选择 | 附加的SELECT语句用于检索整个关联的集合。 此模式适用于许多关联 |
批量 | 其他数量的SELECT语句用于检索整个关联的集合。 每个其他SELECT都会检索固定数量的关联实体。 此模式适用于许多关联 |
这些获取策略可能适用于以下情况:
Hibernate映射获取信息形成了全局获取计划 。 在查询时,我们可以覆盖全局获取计划,但仅适用于LAZY关联 。 为此,我们可以使用访存HQL / JPQL / Criteria指令。 EAGER关联不能被覆盖,因此将您的应用程序与全局获取计划绑定在一起。
Hibernate 3承认LAZY应该是默认的关联获取策略:
默认情况下,Hibernate3对集合使用延迟选择获取,对单值关联使用延迟代理获取。 对于大多数应用程序中的大多数关联而言,这些默认设置有意义。
在注意到与Hibernate 2默认渴望获取有关的许多性能问题后,做出了此决定。 不幸的是,JPA采取了另一种方法,并决定对许多关联为LAZY,而渴望获得一对一的关系。
关联类型 | 默认提取策略 |
---|---|
@OneTMany | 懒 |
@多多多 | 懒 |
@多多 | 急于 |
@OneToOne | 急于 |
EAGER获取不一致
尽管将关联标记为EAGER(将获取职责委托给Hibernate)可能很方便,但还是建议使用基于查询的获取计划。
始终会获取EAGER关联,并且获取策略在所有查询技术之间均不一致。
接下来,我将演示EAGER的获取对于所有Hibernate查询变量的行为。 我将重用我先前在获取策略文章中介绍的实体模型:
产品实体具有以下关联:
@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>();
公司协会被标记为EAGER,并且Hibernate将始终采用获取策略来对其及其所有者实体进行初始化。
持久性上下文加载
首先,我们将使用Persistence Context API加载实体:
Product product = entityManager.find(Product.class, productId);
生成以下SQL SELECT语句:
Query:{[
select
product0_.id as id1_18_1_,
product0_.code as code2_18_1_,
product0_.company_id as company_6_18_1_,
product0_.importer_id as importer7_18_1_,
product0_.name as name3_18_1_,
product0_.quantity as quantity4_18_1_,
product0_.version as version5_18_1_,
company1_.id as id1_6_0_,
company1_.name as name2_6_0_
from Product product0_
inner join Company company1_ on product0_.company_id=company1_.id
where product0_.id=?][1]
使用内部联接检索了EAGER公司关联。 对于M个这样的关联,所有者实体表将被连接M次。
每个额外的连接会增加整体查询的复杂性和执行时间。 如果我们甚至在所有可能的业务场景中都没有使用所有这些关联,那么我们就付出了额外的性能损失,却一无所获。
使用JPQL和条件进行获取
Product product = entityManager.createQuery(
"select p " +
"from Product p " +
"where p.id = :productId", Product.class)
.setParameter("productId", productId)
.getSingleResult();
或搭配
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> cq = cb.createQuery(Product.class);
Root<Product> productRoot = cq.from(Product.class);
cq.where(cb.equal(productRoot.get("id"), productId));
Product product = entityManager.createQuery(cq).getSingleResult();
生成以下SQL SELECT语句:
Query:{[
select
product0_.id as id1_18_,
product0_.code as code2_18_,
product0_.company_id as company_6_18_,
product0_.importer_id as importer7_18_,
product0_.name as name3_18_,
product0_.quantity as quantity4_18_,
product0_.version as version5_18_
from Product product0_
where product0_.id=?][1]}
Query:{[
select
company0_.id as id1_6_0_,
company0_.name as name2_6_0_
from Company company0_
where company0_.id=?][1]}
JPQL和Criteria查询均默认选择获取,因此将为每个EAGER关联发布辅助选择。 关联数越大,单个SELECTS越多,对我们应用程序性能的影响就越大。
休眠标准API
JPA 2.0添加了对Criteria查询的支持,而Hibernate长期以来一直提供特定的动态查询实现 。
如果EntityManager实现委托方法调用旧版Session API,则JPA Criteria实现是从头开始编写的。 这就是为什么Hibernate和JPA Criteria API在类似的查询方案中表现不同的原因。
前面的示例Hibernate Criteria等效项如下所示:
Product product = (Product) session.createCriteria(Product.class)
.add(Restrictions.eq("id", productId))
.uniqueResult();
关联的SQL SELECT是:
Query:{[
select
this_.id as id1_3_1_,
this_.code as code2_3_1_,
this_.company_id as company_6_3_1_,
this_.importer_id as importer7_3_1_,
this_.name as name3_3_1_,
this_.quantity as quantity4_3_1_,
this_.version as version5_3_1_,
hibernatea2_.id as id1_0_0_,
hibernatea2_.name as name2_0_0_
from Product this_
inner join Company hibernatea2_ on this_.company_id=hibernatea2_.id
where this_.id=?][1]}
此查询使用连接抓取策略,而不是选择抓取,通过JPQL / HQL和标准的API使用。
休眠条件和多个EAGER集合
让我们看看将图像收集获取策略设置为EAGER时会发生什么:
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "product", orphanRemoval = true)
@OrderBy("index")
private Set<Image> images = new LinkedHashSet<Image>();
将生成以下SQL:
Query:{[
select
this_.id as id1_3_2_,
this_.code as code2_3_2_,
this_.company_id as company_6_3_2_,
this_.importer_id as importer7_3_2_,
this_.name as name3_3_2_,
this_.quantity as quantity4_3_2_,
this_.version as version5_3_2_,
hibernatea2_.id as id1_0_0_,
hibernatea2_.name as name2_0_0_,
images3_.product_id as product_4_3_4_,
images3_.id as id1_1_4_,
images3_.id as id1_1_1_,
images3_.index as index2_1_1_,
images3_.name as name3_1_1_,
images3_.product_id as product_4_1_1_
from Product this_
inner join Company hibernatea2_ on this_.company_id=hibernatea2_.id
left outer join Image images3_ on this_.id=images3_.product_id
where this_.id=?
order by images3_.index][1]}
休眠条件不会自动将父实体列表分组。 由于存在一对多子表JOIN,因此对于每个子实体,我们将获得一个新的父实体对象引用(在我们当前的持久性上下文中,它们均指向同一对象):
product.setName("TV");
product.setCompany(company);
Image frontImage = new Image();
frontImage.setName("front image");
frontImage.setIndex(0);
Image sideImage = new Image();
sideImage.setName("side image");
sideImage.setIndex(1);
product.addImage(frontImage);
product.addImage(sideImage);
List products = session.createCriteria(Product.class)
.add(Restrictions.eq("id", productId))
.list();
assertEquals(2, products.size());
assertSame(products.get(0), products.get(1));
因为我们有两个图像实体,所以我们将获得两个Product实体引用,它们均指向同一一级缓存条目。
要解决此问题,我们需要指示休眠标准使用不同的根实体:
List products = session.createCriteria(Product.class)
.add(Restrictions.eq("id", productId))
.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY)
.list();
assertEquals(1, products.size());
结论
EAGER的获取策略是一种代码味道。 大多数情况下,它是出于简化的目的而使用,而不考虑长期的性能损失。 提取策略绝不应成为实体映射的责任。 每个业务用例都有不同的实体负载要求,因此,应将获取策略委托给每个单独的查询。
全局提取计划应仅定义LAZY关联,这些关联是在每个查询的基础上提取的。 与始终检查生成的查询策略结合使用,基于查询的获取计划可以提高应用程序性能并降低维护成本。
翻译自: https://www.javacodegeeks.com/2014/12/eager-fetching-is-a-code-smell.html