![](/img/trans.png)
[英]How can I fix N+1 problem in Hibernate & Spring Data JPA?
[英]How can we test for the N+1 problem in JPA/Hibernate?
我有一个 N+1 问题,我想编写某种自动化回归测试,因为它对性能影响很大。
我考虑过监视 EntityManager 并验证它的方法createQuery()
只被调用一次,但是 Hibernate 不要用它来初始化惰性关系,因此它没有用。 我也可以尝试关闭我的存储库和我的服务之间的 JPA 事务(或分离我的实体)并寻找异常,但这确实是一个丑陋的想法。
给我们一个框架,假设我们有一个非常简单的父子关系 model:
@Entity
public class Parent {
…
@OneToMany(fetch = FetchType.LAZY, mappedBy = "parent")
private Collection<Child> children;
}
@Entity
public class Child {
…
@ManyToOne
private Parent parent;
}
一个非常简单的服务:
public class MyService {
…
public void doSomething(Long parentId) {
Parent parent = …; /* retrieve Parent from the database */
doSomeOtherThing(parent.getChildren());
}
}
从数据库中检索父级可以使用以下两个查询:
SELECT parent FROM Parent parent WHERE parent.id = :id;
SELECT parent FROM Parent parent JOIN FETCH parent.children WHERE parent.id = :id;
当我用第一个查询而不是第二个查询检索我的父实体时,我如何编写一个崩溃的测试?
作为选项,您可以在测试中验证查询(获取、更新、插入)的计数
repository.findById(10L);
SessionFactory sf = em.getEntityManagerFactory().unwrap(SessionFactory.class);
Statistics statistics = sf.getStatistics();
assertEquals(2L, statistics.getQueryExecutionCount());
查看休眠统计
请参阅以下解决方案,该解决方案依赖于包装您的DataSource
https://vladmihalcea.com/how-to-detect-the-n-plus-one-query-problem-during-testing/
我想“回归测试”是指可能由 JUnit 启动的实际测试。
在单元测试中处理它的一般方法可能是:
在运行查询以检索“父”实体后,使用PersistenceUnitUtil ,您可以断言“子”是否已被急切加载:
PersistenceUnitUtil pu = em.getEntityManagerFactory().getPersistenceUnitUtil();
assertTrue(pu.isLoaded(parent, "children"));
您可以使用的另一个选项是在初始提取之后、但在引用实体上的任何潜在延迟加载字段之前清除EntityManager
。 这有效地断开了代理以执行延迟加载,并且如果初始查询中未使用JOIN FETCH
,则应导致您的测试抛出异常。
您的测试最终将类似于以下内容(用 Kotlin 编写)
class MyRepositoryTest @Autowired constructor(
myRepository: MyRepository,
entityManager: EntityManager
) {
@Test
fun `When load children will eagerly fetch`() {
val parent = myRepository.loadParent()
entityManager.clear()
// This line should throw if children are being lazily loaded
assertThat(parent?.children, equalTo(listOf(Child(1), Child(2))))
}
}
我写了一个小库,可以断言 SQL 查询的计数(SELECT,INSERT,..)在你的 Spring 测试中由 Hibernate 生成,这样,只要 SQL 语句在你的测试中发生变化,你就会收到警告,并且防止 N+1 选择。 你可以在这里看看这个项目
演示目的的测试示例:
@Test
@Transactional
@AssertHibernateSQLCount(selects = 1) // We want 1 SELECT, will warn you if you're triggering N+1 SELECT
void fetch_parents_and_children() {
parentRepository.findAll().forEach(parent ->
parent.getChildren().size()
);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.