Hibernate создает новый объект без необходимости при работе в другом потоке - PullRequest
0 голосов
/ 02 мая 2018

Я использую Spring Data JPA, выполняю следующие результаты теста в нелогичном поведении

@Test
public void testAsync() throws ExecutionException, InterruptedException {
    Job job = jobRepository.save(new Job());
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(10);
    long origJobID = job.getId();
    executor.initialize();
    Future<?> wait = executor.submit(() -> {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Job outcome = jobRepository.save(job.setStopTime(Instant.now()));
        // this assertion fails, Hibernate requested a new ID and persisted a new entity ... even though I am reusing the same instance with an ID already populated
        assertEquals(origJobID, outcome.getId().longValue());
    });
    wait.get();
}

Поскольку jobRepository предоставляет только интерфейс save(), как пользователь I службы я могу вызывать этот метод только для ВСТАВЛЕНИЯ или ОБНОВЛЕНИЯ моей сущности ... как это возможно, что управляющий сущностью просто игнорирует этот факт? что у моей сущности уже есть идентификатор, и он создает дубликат строки?

Если заглянуть дальше в кодовую базу Hibernate, она появится в новом потоке, persistentContext будет стерт. Поэтому моя сущность превращается в состояние ОТКЛЮЧЕНО, что касается DefaultMergeEventListener ... создавая каскад решений, которые необъяснимым образом приводят к генерации нового идентификатора

Ссылка на конкретный код I находится по адресу: https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java#L109

Если запустить из нового потока, контекст персистентности Hibernate будет пустым ... что нормально ... но тогда я не понимаю, почему моя сущность теперь считается DETACHED ...

Дополнительная выдержка из исходного кода Hibernate: по умолчанию https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java#L293

if ( result == null ) {
            //TODO: we should throw an exception if we really *know* for sure
            //      that this is a detached instance, rather than just assuming
            //throw new StaleObjectStateException(entityName, id);

            // we got here because we assumed that an instance
            // with an assigned id was detached, when it was
            // really persistent
            entityIsTransient( event, copyCache );
        }

РЕДАКТИРОВАТЬ Если это связано с тем фактом, что транзакции еще не зафиксированы, и новый поток не использует ту же транзакцию ... есть ли способ форсировать транзакцию в коде?

Примечание: вызов JpaRepository.saveAndFlush() не решает проблему

РЕДАКТИРОВАТЬ 2 Я использую встроенный h2 для этого теста, в любом случае я ожидал, что saveAndFlush() передаст транзакцию в базу данных (встроенную или на другой стороне мира), чтобы несколько потоков могут использовать JpaRepository для просмотра состояний, сохраненных другими потоками, верно?

РЕДАКТИРОВАТЬ 3 Глядя на другие подобные вопросы, он помечает сам метод теста как @ Транзакция (распространение = NOT_SUPPORTED) заставляет базовый менеджер транзакций выполнить JpaRepository.save(). ... это до сих пор сбивает с толку ... с чего начинался транзакционный метод тестирования? то есть почему транзакция не была зафиксирована с самого начала?

1 Ответ

0 голосов
/ 02 мая 2018

Очень грубое представление о том, как это работает:

  • У вас есть несколько тем
  • Транзакции обычно привязаны к потоку (каждый поток получает свою собственную транзакцию)
  • EntityManager s обычно привязаны к транзакции (каждая транзакция имеет свой собственный EntityManager, который видит вещи в соответствии с этой транзакцией)

Что вы делаете, это

  1. Скажите менеджеру сущностей в потоке 1, что вы хотите сделать сущность A постоянной / сохранить ее изменения (это то, что save () означает в Spring Data)
  2. Передать сущность A в поток 2
  3. Скажите менеджеру объектов в потоке 2, что вы хотите сделать объект A постоянным / сохранить его изменения

Поскольку менеджеры сущностей находятся в разных транзакциях, а поток 1 еще не завершил свою транзакцию при выполнении 3., с точки зрения потока 2 сущность А еще не является постоянной; поэтому он будет интерпретировать save() (или saveAndFlush()) как «сделать объект A постоянным», а не как «сохранять изменения в объекте A». А создание объекта A постоянным подразумевает назначение нового идентификатора объекту A, если необходимо.

Вы должны быть в состоянии выполнить эту работу, убедившись, что поток 1 уже зафиксировал при выполнении шага 3. В любом случае, я не рекомендую передавать ссылки на управляемые сущности между потоками, потому что это очень сложно.

О разграничении транзакций в Spring Data вы можете прочитать https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions для получения дополнительной информации, но в основном это включает в себя создание каждой транзакции методом, а затем аннотирование этого метода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...