【问题标题】:How can we test for the N+1 problem in JPA/Hibernate?我们如何测试 JPA/Hibernate 中的 N+1 问题?
【发布时间】:2019-04-26 17:54:48
【问题描述】:

我有一个 N+1 问题,我想编写某种自动化回归测试,因为它对性能影响很大。

我想过监视 EntityManager 并验证它的方法 createQuery() 只调用了一次,但是 Hibernate 不使用它来初始化惰性关系,因此它不起作用。我也可以尝试关闭我的存储库和我的服务之间的 JPA 事务(或分离我的实体)并注意异常,但这确实是一个丑陋的想法。

给我们一个框架,假设我们有一个非常简单的父子模型:

@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;

当我使用第一个查询而不是第二个查询检索父实体时,我如何编写一个崩溃的测试?

【问题讨论】:

  • @Sambit 我不是要求解决 N+1 问题,而是要求一种自动检测它的方法。我已经阅读了您所指的问题,但在那里找不到答案。
  • 您可以尝试检查getChildren() 是否实际返回PersistentCollection,如果是,则wasInitialized() 是否返回true。如果该持久性集合未初始化,则任何访问都可能导致延迟加载(可能在某些情况下不会发生这种情况,例如,在调用 size() 时并取决于您的映射 - 例如,它是否额外延迟,但是很有可能)。

标签: java hibernate jpa regression-testing


【解决方案1】:

作为选项,您可以验证测试中的查询计数(获取、更新、插入)

 repository.findById(10L);

 SessionFactory sf = em.getEntityManagerFactory().unwrap(SessionFactory.class);
 Statistics statistics = sf.getStatistics();

 assertEquals(2L, statistics.getQueryExecutionCount());

hibernate statistic

【讨论】:

  • one SO answer 中提到的,您应该先“启用”统计信息,否则不会记录任何内容
【解决方案2】:

请参阅以下解决方案,该解决方案依赖于包装您的 DataSource https://vladmihalcea.com/how-to-detect-the-n-plus-one-query-problem-during-testing/

【讨论】:

    【解决方案3】:

    您可以使用的另一个选项是在初始提取之后但在引用实体上任何可能延迟加载的字段之前清除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))))
        }
    }
    

    【讨论】:

      【解决方案4】:

      我想“回归测试”是指可能由 JUnit 启动的实际测试。

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

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

      【讨论】:

        【解决方案5】:

        在运行查询以检索“父”实体后,使用PersistenceUnitUtil,您可以断言“子”是否已被热切加载:

        PersistenceUnitUtil pu = em.getEntityManagerFactory().getPersistenceUnitUtil();
        assertTrue(pu.isLoaded(parent, "children"));
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-08-26
          • 2015-12-03
          • 2020-08-21
          • 2013-10-19
          • 2015-10-20
          • 2021-11-06
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多