jpa获取session
介绍
最近,我一直在与JPA 2中的FETCH JOINS一起使用,以期从数据库中急切地获取数据,并且我学到了很多关于为什么在日常操作中应避免使用Fetch Joins的知识。
今天的博客文章谈论了我在Fetch上的经历和学习(主要基于当我在查询中有很多FETCH JOINS时获得的评论)。
问题陈述
在我们的项目中,有一种情况是从定义了许多集合值关联的数据库(OneToMany,ManyToMany,也称为ToMany关联)中获取实体。 这是实体外观的概念图(为清楚起见,省略了getter和setter)。 这是实体的极其简化的示例。 在实际情况下,我们大约有11个关联。
public class ItemRecord {@Idprivate String itemId;@Column(name="author")private String author;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private List costs;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private List notes;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private List stats;}
关于上述实体,有几件事需要注意:
- 它具有3个收藏珍贵的协会。
- 所有这些关联都被延迟获取,因为JPA中“集合有价值的关联”的默认获取策略是“惰性”。
在我们的业务实现中,我们有一个转换器,该转换器将DAO层返回的值转换为Business DTO。
因此,我们的业务方法的算法如下:
@TransactionAttribute
public List searchItemRecords (SearchCriteria sc) {List ir = itemRecordDao.search(sc);List convertedData = recordConverter.convert(ir);return convertedData;
}
请注意,整个方法都在Transaction内部运行。
每当我们从数据库中获取数据时,都不会急切地获取与成本,统计信息等相关的数据。 但是我们的ItemInformation DTO期望所有数据。 因此,当首次调用getCosts或getStatistics时,持久性提供程序(在本例中为Hibernate)向数据库激发查询以获取指定的数据。 这为我们创建了N + 1个选择查询问题。 如果您不熟悉N + 1选择或需要刷新,可以在DZone上查看此文章 。
解
我们大多数人,包括我在内,都会选择N + 1选择问题的最快,最简单的解决方案,即使用Fetch Join。 在Internet上发布的不同博客/文章中也有很多建议。
因此,我也采用了相同的方法。 就我而言,这至少是一种糟糕的方法。
首先让我们看看如何使用FETCH JOIN。
使用Fetch Join之前的查询如下:
SELECT item FROM ItemRecord item WHERE author=:author;
请注意,查询采用JPA形式。
该查询未获取集合值。 结果,在翻译器中,当我们对每个ItemRecord执行getCosts时,将触发类似于以下的查询:
SELECT cost FROM Cost where itemId = :itemId
因此,如果我们有3个ItemRecords,则激发到数据库的SELECT查询总数为:
- 1用于获取所有ItemRecords
- 3个用于获取每个ItemRecord的成本
- 3用于获取每个ItemRecord的注释
- 3个用于获取每个ItemRecord的统计信息
即(3乘3)+ 1
当将其转换为N乘以M +1时,
哪里,
- N是找到的主要实体记录的数量
- M是主要实体中Collection值关联的数量
- 1个查询,用于获取所有主要实体
在实际情况下,我们有11个关联。 因此,对于每个主要ItemRecord实体,我们将触发11个SELECT查询。 激发的查询数量乘以找到的每个ItemRecord。
使得集合值关联成为EAGER是不可行的,因为在实体上运行的许多其他查询仅需要选定的数据。
这不是最佳解决方案。 必须做一些事情,很多互联网文章建议使用FETCH JOINS,因为它们最容易实现并解决了(N Time M +1)个查询问题。
因此,我决定在查询中使用FETCH Joins来针对给定场景急切地获取所有数据。
该查询类似于:
SELECT item FROM ItemRecordJOIN FETCH item.costs,
JOIN FETCH item.notes,
JOIN FETCHitem.stats;
跨栏1
我很快意识到该查询在我的情况下将无法使用,因为我可以拥有一个与之不相关的统计信息,而在这种情况下,上述查询不会向我返回那个ItemRecord(因为ORM的工作方式)因此我将获得不完整的数据。
因此,接下来我转到了LEFT JOIN FETCH,即使某些关联关系为空,它也将给我ItemRecord实体返回。 查询如下所示:
SELECT item FROM ItemRecordLEFT JOIN FETCH item.costs,
LEFT JOIN FETCH item.notes,
LEFT JOIN FETCH item.stats;
跨栏2
当我运行单元测试以测试上述查询时,出现了异常:
javax.persistence.PersistenceException: org.hibernate.HibernateException: cannot simultaneously fetch multiple bags
问题是什么?
问题是我在实体中使用列表作为集合类型。 使用列表会混淆JPA /Hibernate。 这篇文章很好地记录了这种困惑。
为了解决此问题,我选择使用Set而不是List,主要是因为它是上述博客文章提供的三种解决方案中最简单的方法,并且也很有意义(至少在我实现时)。
因此,我将Entity更改为Set而不是List,并且修改后的实体如下所示:
public class ItemRecord {@Idprivate String itemId;@Column(name="author")private String author;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private Set costs;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private Set notes;@OneToMany(cascade = CascadeType.ALL, mappedBy = "item")private Set stats;}
我再次运行测试用例,并且测试查询的测试用例成功。 耶皮 但是,我的另一个测试用例正在测试注释部分中的条目失败。 看一下测试用例,我意识到我需要按照特定的顺序输入数据,并且我使用的是HashSet,但没有顺序。 解决方案很简单。 使用LinkedHashSet维护元素的顺序。
使用LinkedHashSet可以解决问题,并且我的测试用例通过了。
我很高兴,但我的幸福短暂。
跨栏3
我还有另一个测试用例,对于给定的ItemRecord来说,它需要3个成本对象。 我转到设置实现后,测试立即开始失败。 事实证明,我的哈希码不正确,并且我的“成本实体”的实现等于“实现”,“成本实体”为两个不同的实体返回相同的哈希码,结果,由于Set不允许重复值,因此仅持久保留了一个实体。
因此,我接下来要做的就是为我的所有实体使用适当的HashCode和Equals实现。
最终关卡
最终,当我所有的测试用例开始通过时,我进行了代码审查,并将其发送给团队。
第一个吓到我的是我的技术主管。 :)
他只是生气地看着FETCH JOINS。 原因? 原因是LEFT FETCH JOINs返回所有数据的笛卡尔积。 随着我们生产中的数据量的增加,甚至无法支持ItemRecord上的多个选择将成为一场噩梦。 整个问题可以在此博客文章中轻松理解。
因此,我试图解决性能问题,事实证明我实际上是在制造更大的性能问题。 :)
删除了转移到FETCH JOIN的整个解决方案,因此决定进一步研究为什么我们需要UI上的全部数据,以及为什么不能将获取数据分成较小的专用事务。
摘要:
使用Fetch Joins的整个过程使我很好地了解了Joins的总体工作原理以及使用它们时的期望。
希望您喜欢这篇博客文章。 如果您想继续阅读有趣的帖子,可以关注我的博客。
翻译自: https://www.javacodegeeks.com/2013/07/jpa-2-fetch-joins-and-whether-we-should-use-them.html
jpa获取session