eager_EAGER提取是一​​种代码异味

news/2024/7/24 10:11:43 标签: java, python, 数据库, hibernate, mysql
eager

eager

介绍

Hibernate获取策略确实可以使几乎没有爬网的应用程序和响应速度很快的应用程序有所不同。 在这篇文章中,我将解释为什么您应该更喜欢基于查询的获取而不是全局获取计划。

取得101

Hibernate定义了四种关联检索策略:

提取策略 描述
加入原始SELECT语句中的关联是OUTER JOINED
选择 附加的SELECT语句用于检索关联的实体(实体)
子选择 附加的SELECT语句用于检索整个关联的集合。 此模式适用于多个关联
批量 其他数量的SELECT语句用于检索整个关联的集合。 每个其他SELECT都会检索固定数量的关联实体。 此模式适用于多个关联

这些获取策略可能适用于以下情况:

  • 关联总是与其所有者一起初始化(例如EAGER FetchType)
  • 导航未初始化的关联(例如LAZY FetchType),因此必须使用辅助SELECT检索关联

Hibernate映射获取信息形成了全局获取计划。 在查询时,我们可以覆盖全局获取计划,但仅适用于LAZY关联。 为此,我们可以使用访存HQL / JPQL / Criteria指令。 EAGER关联不能被覆盖,因此将您的应用程序与全局获取计划绑定在一起。

Hibernate 3承认LAZY应该是默认的关联获取策略:

默认情况下,Hibernate3对集合使用延迟选择获取,对单值关联使用延迟代理获取。 对于大多数应用程序中的大多数关联而言,这些默认设置有意义。

在注意到与Hibernate 2默认渴望获取有关的许多性能问题后,做出了此决定。 不幸的是,JPA采取了不同的方法,并决定对许多关联为LAZY,而渴望获得一对一的关系。

关联类型 默认提取策略
@OneTMany
@多多多
@多多急于
@OneToOne 急于

EAGER获取不一致

虽然将关联标记为EAGER(将获取职责委托给Hibernate)可能很方便,但还是建议使用基于查询的获取计划。

始终会获取EAGER关联,并且获取策略在所有查询技术之间均不一致。

接下来,我将演示在所有Hibernate查询变体中EAGER的获取行为。 我将重用我在获取策略文章中先前介绍的相同的实体模型:

javacodegeeks.com/wp-content/uploads/2014/12/product2.png">

产品2

产品实体具有以下关联:

java;wrap-lines:false">@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加载实体:

java">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和条件进行获取

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

或搭配

java">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越多,对我们应用程序性能的影响就越大。

Hibernate标准API

JPA 2.0添加了对Criteria查询的支持,而Hibernate长期以来一直提供特定的动态查询实现。

如果EntityManager实现委托方法调用旧版Session API,则JPA Criteria实现是从头开始编写的。 这就是为什么Hibernate和JPA Criteria API在类似的查询方案中表现不同的原因。

前面的示例Hibernate Criteria等效项如下所示:

java">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使用。

Hibernate条件和多个EAGER集合

让我们看看将图像收集获取策略设置为EAGER时会发生什么:

java;wrap-lines:false">@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]}

Hibernate条件不会自动将父实体列表分组。 由于存在一对多子表JOIN,因此对于每个子实体,我们将获得一个新的父实体对象引用(在当前的持久性上下文中,它们均指向同一对象):

java">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实体引用,它们均指向同一一级缓存条目。

要解决此问题,我们需要指示Hibernate标准使用不同的根实体:

java">List products = session.createCriteria(Product.class)
	.add(Restrictions.eq("id", productId))
	.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY)
	.list();
assertEquals(1, products.size());

结论

EAGER的获取策略是一种代码味道。 通常,它是出于简化的目的而使用的,而没有考虑长期的性能损失。 提取策略绝不应成为实体映射的责任。 每个业务用例具有不同的实体负载要求,因此,应将获取策略委托给每个单独的查询。

全局获取计划应仅定义LAZY关联,这些关联是在每个查询的基础上获取的。 结合始终检查生成的查询策略,基于查询的获取计划可以提高应用程序性能并降低维护成本。

  • Hibernate和JPA可用的代码。

翻译自: https://www.javacodegeeks.com/2014/12/eager-fetching-is-a-code-smell.html

eager


http://www.niftyadmin.cn/n/1707823.html

相关文章

钉钉+百数私有云,助力企业数据中台搭建

数据中台的搭建&#xff0c;成为了近些年企业数字化转型的重要举措。尤其对于传统大型集团、企业来说&#xff0c;经过长期发展&#xff0c;往往拥有多个产业方向&#xff0c;项目众多管理复杂。除了配置例如钉钉这样的协同办公平台&#xff0c;拥有企业独立的数据中心&#xf…

远程连接数据库---Host * is not allowed to connect to this MySQL server

use mysql; update user set host % where user root; FLUSH RIVILEGES 发现第三步报错,选择重启服务,成功连接!

jaxb_JAXB众所周知的秘密

jaxb介绍 我重新发现了Java为大众提供的库。 当我第一次阅读该规范时&#xff0c;我很困惑&#xff0c;以为我需要所有这些特殊工具来实现。 我最近发现&#xff0c;只需要一些注释和一个POJO。 杰克斯 JAXB代表用于XML绑定的Java体系结构。 这种体系结构允许开发人员将来自类的…

多线程面试题_Java中的线程本地存储

多线程面试题开发人员中鲜为人知的功能之一是线程本地存储。 这个想法很简单&#xff0c;需要它的场景是……我们需要的数据在线程中很好。 如果我们有两个线程&#xff0c;则它们引用相同的全局变量&#xff0c;但我们希望它们具有彼此独立初始化的单独值。大多数主要的编程语…

百数私有云平台助力电商领域企业整体业务上云

我国近年来的电子商务交易额增长率一直保持快速增长势头&#xff0c;特别是网络零售市场更是发展迅速。从“双十一”、“618”到现在数不尽的电商节&#xff0c;业务发展迅猛的同时&#xff0c;商家们也需要处理越来越多的订单数据。如何选择一款配合高效的订单管理系统&#x…

IDEA--连接远程windows服务器

1.服务器tomcat配置 Tomcat下catalina.bat 添加下面代码 set CATALINA_OPTS-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port1099 -Dcom.sun.management.jmxremote.rmi.port1099 -Dcom.sun.management.jmxremote.sslfalse -Dcom.sun.management.jmxremot…

lambda表达式_您会后悔对Lambdas应用重载!

lambda表达式编写好的API很难。 非常辛苦。 如果您想让用户喜欢您的API&#xff0c;则必须考虑很多事情。 您必须在以下两者之间找到适当的平衡&#xff1a; 用处 易用性向后兼容向前兼容性 之前&#xff0c;在我们的文章&#xff1a; 如何设计良好的常规API中&#xff0c;我们…

问题解决SDK Platform Tools component is missing! (转)

Eclipse 就出现 "SDK Platform Tools component is missing! ..."的提示框 &#xff01; 然后就发现&#xff0c;所有Android项目都不会自动编译出R.java文件。 那么如何解决呢&#xff1f; 其实这是由于 ADT工具版本 与 Android SDK Platform-tools Version不匹配造…