Почему мой запрос JQL возвращает результат, отличный от эквивалентного запроса CriteriaBuilder?

#hibernate #jpa #dropwizard

#переход в спящий режим #jpa #dropwizard

Вопрос:

Я использую Dropwizard Hibernate, и у меня возникают проблемы с моими тестами. Я максимально упростил этот пример. Я создаю Foo , обновляю его, а затем пытаюсь извлечь его. Использование необработанного запроса дает правильный результат, но эквивалентный запрос CriteriaBuilder не улавливает обновление. Что я делаю не так?

     @Test
    public void testFoo() {
        String id = "12345";

        // Create
        Foo foo = Foo.builder()
            .id(id)
            .name("old-name")
            .build();
        sessionFactory.getCurrentSession().replicate(foo, ReplicationMode.EXCEPTION);

        // Update
        database.inTransaction(() -> {
            CriteriaBuilder cb = sessionFactory.getCurrentSession().getCriteriaBuilder();
            CriteriaUpdate<Foo> update = cb.createCriteriaUpdate(Foo.class);
            Root<Foo> root = update.from(Foo.class);

            update.set(Foo_.name, "new-name");
            update.where(cb.equal(root.get(Foo_.id), id));
            int updated = sessionFactory.getCurrentSession().createQuery(update).executeUpdate();
        });

        // Select
        database.inTransaction(() -> {
            sessionFactory.getCurrentSession().flush(); // Not sure if this matters

            String newName = (String) sessionFactory.getCurrentSession()
                .createQuery("select name from Foo where id=:id")
                .setParameter("id", id)
                .getSingleResult();

            assertEquals("new-name", newName);
            log.error("New name is "   newName);

            CriteriaBuilder cb = sessionFactory.getCurrentSession().getCriteriaBuilder();
            CriteriaQuery<Foo> cq = cb.createQuery(Foo.class);
            Root<Foo> root = cq.from(Foo.class);
            cq.where(cb.equal(root.get(Foo_.id), id));
            Query query = sessionFactory.getCurrentSession().createQuery(cq);

            Foo foo2 = (Foo) query.getSingleResult();
            log.error("New name is "   foo2.getName()); // Prints "old-name"
        });
    }
  

Вот мой установочный код:

 @ExtendWith(DropwizardExtensionsSupport.class)
public class UpdateTest {
    private SessionFactory sessionFactory;

    public DAOTestExtension database = DAOTestExtension.newBuilder()
        .addEntityClass(Foo.class)
        .build();

    @BeforeEach
    public void setup() {
        sessionFactory = database.getSessionFactory();
    }
...
}
  

Я также могу показать Foo класс, но это не очень интересно.

Комментарии:

1. Что такое Foo_.id ? является ли подчеркивание опечаткой?

2. @Guillaume Это называется статической метамоделью: docs.jboss.org/hibernate/orm/5.0/topical/html/metamodelgen /… . Foo._id эквивалентно «id» здесь.

Ответ №1:

Foo Экземпляр все еще находится в кэше L1, т. е. в текущем сеансе, который не затрагивается инструкцией update. Поскольку Hibernate / JPA должен сохранять идентификацию для объектов entity сеанса, он не может очистить сеанс из-за инструкции update . Обычно требуется очистить сеанс или обновить экземпляры после инструкции update, чтобы снова отразить текущее состояние.

Попробуйте вместо этого выполнить sessionFactory.getCurrentSession().clear(); в начале вашей транзакции чтения

Комментарии:

1. Спасибо, это сработало! Таким образом, обновления в сеансе (либо через необработанный запрос, либо через CriteriaBuilder ) не будут отражены в последующих запросах, если я clear() ? Тогда это обычный шаблон для clear() перед каждым запросом? Знакомы ли вы с какими-либо ресурсами, которые подробно объясняют все это? (Быстрый поиск в Google выдал множество незавершенных статей.)

2. Извините, если мой последний комментарий был непонятен. Я попытался выполнить обновление с помощью CriteriaUpdate , ожидая, что оно коснется кэша (см. Обновленный код). Но я получаю тот же результат. Это ожидаемо? Если это так, я не понимаю, как должен работать кэш!

3. Проблема в том, что инструкции JPQL / SQL DML не влияют на объекты entity непосредственно в кэше L1. Если вы запрашиваете объекты после выполнения DML, Hibernate по праву сохранит идентификатор объекта и вместо загрузки состояния из результата запроса просто вернет вам объект из кэша L1, если он доступен. Если вы выполняете инструкции DML, вам следует очистить сеанс, если вы хотите запрашивать объекты с тем же сеансом. Обратите внимание, что обычно это не проблема, потому что сеансы легкие и должны быть недолговечными, т. Е. только для одной транзакции.

4. Спасибо, Кристиан, но я нигде не делаю обновления DML, не так ли? Я полагаю, вы видели мой последний комментарий?

5. Ха, спасибо! Очевидно, я был сбит с толку тем, что представляет собой оператор DML. Если я вызываю session.update() напрямую, это работает так, как ожидалось. Большое спасибо!