简体   繁体   English

使用 JPA 和 Hibernate 在测试期间检测 N+1 查询问题

[英]Detecting N+1 query issues during testing with JPA and Hibernate

We're using https://github.com/vladmihalcea/db-util which is a great tool, but we're facing a challenge regarding Session cache, that as mentioned in another question, it can't be disabled .我们正在使用https://github.com/vladmihalcea/db-util这是一个很棒的工具,但是我们面临着 Session 缓存的挑战,正如另一个问题中提到的,它不能被禁用

So, the following test fails because findOne fetches the object from the session cache:因此,以下测试失败,因为findOne从 session 缓存中获取 object:

    @Test
    public void validateQueries() {
        // when (scenario definition)
        TestObject testObject = new TestObject(1L);
        repository.save(testObject);
        SQLStatementCountValidator.reset();

        repository.findOne(1L);
        SQLStatementCountValidator.assertSelectCount(1);
    }

There's a workaround calling entityManager.clear() every time SQLStatementCountValidator.reset() is called.每次调用SQLStatementCountValidator.reset()时,都会调用entityManager.clear()的解决方法。

Now, the workaround is fine, but error-prone because now we have to inject EntityManager as a dependency of our tests and remember to call entityManager.clear() after saving all the objects that represent our scenario.现在,解决方法很好,但容易出错,因为现在我们必须注入 EntityManager 作为测试的依赖项,并记住在保存代表我们场景的所有对象后调用entityManager.clear()

Questions问题

  1. What would be the best way of achieving this?实现这一目标的最佳方法是什么?
  2. Would you expect SQLStatementCountValidator to also clear the entityManager?您是否希望 SQLStatementCountValidator 也清除 entityManager?

Here you can check the log statements (the last one)在这里可以查看日志语句(最后一条)

09:59.956 [main] [TRACE] o.h.e.i.AbstractSaveEventListener - Transient instance of: TestObject
09:59.957 [main] [TRACE] o.h.e.i.DefaultPersistEventListener - Saving transient instance
09:59.962 [main] [TRACE] o.h.e.i.AbstractSaveEventListener - Saving [TestObject#<null>]
Hibernate: 
    insert 
    into
        test_object
        (id, creation_time, "update_time", "name") 
    values
        (null, ?, ?, ?)
10:00.005 [main] [TRACE] o.h.e.i.AbstractFlushingEventListener - Flushing session
10:00.005 [main] [DEBUG] o.h.e.i.AbstractFlushingEventListener - Processing flush-time cascades
10:00.007 [main] [DEBUG] o.h.e.i.AbstractFlushingEventListener - Dirty checking collections
10:00.007 [main] [TRACE] o.h.e.i.AbstractFlushingEventListener - Flushing entities and processing referenced collections
10:00.011 [main] [TRACE] o.h.e.i.AbstractFlushingEventListener - Processing unreferenced collections
10:00.011 [main] [TRACE] o.h.e.i.AbstractFlushingEventListener - Scheduling collection removes/(re)creates/updates
10:00.011 [main] [DEBUG] o.h.e.i.AbstractFlushingEventListener - Flushed: 0 insertions, 0 updates, 0 deletions to 1 objects
10:00.011 [main] [DEBUG] o.h.e.i.AbstractFlushingEventListener - Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
10:00.015 [main] [TRACE] o.h.e.i.AbstractFlushingEventListener - Executing flush
10:00.015 [main] [TRACE] o.h.e.i.AbstractFlushingEventListener - Post flush
10:02.780 [main] [TRACE] o.h.e.i.DefaultLoadEventListener - Loading entity: [TestObject#1]
10:08.439 [main] [TRACE] o.h.e.i.DefaultLoadEventListener - Attempting to resolve: [TestObject#1]
10:08.439 [main] [TRACE] o.h.e.i.DefaultLoadEventListener - Resolved object in session cache: [TestObject#1]

com.vladmihalcea.sql.exception.SQLSelectCountMismatchException: Expected 1 statements but recorded 0 instead!

This is how the workaround code looks like:解决方法代码如下所示:

    @Test
    public void validateQueries() {
        // when (scenario definition)
        TestObject testObject = new TestObject(1L);
        repository.save(testObject);
        entityManager.clear();
        SQLStatementCountValidator.reset();

        repository.findOne(1L);
        SQLStatementCountValidator.assertSelectCount(1);
    }

Transaction handling事务处理

Each test should manage transactions.每个测试都应该管理事务。 So, you should remove the @Transactional annotation you added at the class level.因此,您应该删除在 class 级别添加的@Transactional注释。

So, you inject a TransactionTemplate bean:所以,你注入一个TransactionTemplate bean:

@Autowired
private TransactionTemplate transactionTemplate;

And, then you save the entity in one transaction:然后,您将实体保存在一个事务中:

@Test
public void validateQueries() {
    try {
        transactionTemplate.execute((TransactionCallback<Void>) transactionStatus -> {
            TestObject testObject = new TestObject(1L);
            repository.save(testObject);

            return null;
        });
    } catch (TransactionException e) {
        LOGGER.error("Failure", e);
    }

    SQLStatementCountValidator.reset();
    repository.findOne(1L); 
    SQLStatementCountValidator.assertSelectCount(1);
}

You can extract the transaction handling logic in a base class method to simplify the exception handling.您可以在基本 class 方法中提取事务处理逻辑以简化异常处理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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