繁体   English   中英

我们如何测试 JPA/Hibernate 中的 N+1 问题?

[英]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 启动的实际测试。

在单元测试中处理它的一般方法可能是:

  • 将 hibernate.show_sql 配置为 true
  • 拦截日志消息,如intercept 中所述
  • 扫描日志文件
    • 您希望避免的特定查询
    • 类似查询的数量

在运行查询以检索“父”实体后,使用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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM