LINUX.ORG.RU

Hibernate: merge - потеря данных.

 , ,


0

1

Понедельник вечер, а я уже не соображаю.

Что я делаю не правильно? Кто знает Hibernate хорошо, может подскажете?

EntityManager em = entityManagerProvider.get();
em.getTransaction().begin();

Collections.sort(noticeDrafts, new Comparator<NoticeDraft>() {
    @Override
    public int compare(NoticeDraft d0, NoticeDraft d1) {
        return d0.getId().compareTo(d1.getId());
    }
});
Collections.sort(numberNotices, new Comparator<NumberNotice>() {
    @Override
    public int compare(NumberNotice n0, NumberNotice n1) {
        return n0.getId().compareTo(n1.getId());
    }
});

LOGGER.severe("Отсортированы ПИ: " + noticeDrafts.toString());
LOGGER.severe("Отсортированы ИИ: " + numberNotices.toString());

final Iterator<NoticeDraft> draftIterator = noticeDrafts.iterator();
final Iterator<NumberNotice> noticeIterator = numberNotices.iterator();

while (draftIterator.hasNext() && noticeIterator.hasNext()) {
    final NoticeDraft draft = draftIterator.next();
    final NumberNotice notice = noticeIterator.next();

    draft.setStatusCode(NoticeDraft.DRAFT_STATUS_6_MUTATED);
    draft.setMutatedBy(mutatedBy);
    draft.setMutationDate(new Date());
    draft.setNumberNoticeFk(notice);
    em.merge(draft);

    LOGGER.severe("Добавлен ИИ {" + draft.getNumberNoticeFk().getId() + "} к ПИ {" + draft.getId() + "}");

    final NoticeDraftStatusChange statusChange = new NoticeDraftStatusChange();
    statusChange.setNoticeDraft(draft);
    statusChange.setModificationDate(new Date());
    statusChange.setStatusCode(NoticeDraft.DRAFT_STATUS_6_MUTATED);
    statusChange.setUsername(mutatedBy);
    em.persist(statusChange);
}
em.getTransaction().commit();

В логе:

02-Jul-2018 19:58:09.407 SEVERE [http-nio-8080-exec-9] <вырезано>ServiceImpl.changePIstatusToMutated Добавлен ИИ {1479} к ПИ {76}
02-Jul-2018 19:58:09.411 SEVERE [http-nio-8080-exec-9] <вырезано>ServiceImpl.changePIstatusToMutated Добавлен ИИ {1480} к ПИ {77}

Но БД меня крутила на своём:

Id, Name, NumberNoticeFk
'76', '<вырезано>.117-18ПИ', NULL
'77', '<вырезано>.118-18ПИ', NULL

Почему после merge() и перед commit() сущность имеет ссылку на родителя (NoticeDraft на NumberNotice), а в БД ничего нема?

Интересно то, что если выполнять пошагово в отладчике, в БД всё прекрасно сохраняется и поле NumberNoticeFk не NULL, а соответствующий ID.

Софт, конечно, немножко устарел: MySQL 5.7, Hibernate 3.6.5, OpenJDK 8 (обновлена, но languageLevel установлен для проекта на 7).

Deleted

Последнее исправление: merhalak (всего исправлений: 1)

Ответ на: комментарий от Deleted

Получается, по хорошему, мне надо проверить getNumberNoticeFk в возвращаемом значении из функции, а не тупить, что аргумент будет изменен по ссылке?

Deleted
()
Ответ на: комментарий от Deleted

Вот точно не скажу, но обрати на это момент внимание.

зы. т.к. я наторчался отладчиком в этом чуде, и стараюсь его (да в и общем jpa) избегать.

Deleted
()
Ответ на: комментарий от Deleted

всякую фигню с кастомными запросами, вроде jdbi (но это было до моего прихода в проект, хотя штука приятная), или вообще самоделку типа https://github.com/wayerr/sqlfiles (это пока не юзается, но точно такую же идею мы юзали на прошлой работе)

но у нас специфика - сложные запросs

Deleted
()
Ответ на: комментарий от Deleted

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

Deleted
()

Уже плохо знаю hibernate, давно не писал, но...

draft.setNumberNoticeFk(notice);

Зачем суфикс Fk? Читающего это сильно сбивает с толку, ведь где хранить Fk это уже дело мэпинга - деталь имплементации, ненужно такие вещи выносить в именования публичных методов (интерфейс объекта).

Мэпинг ты не скинул (pojo с аннотациями), вдруг там ошибка.

В hibernate можно включить логирование генерируемых sql, как именно уже не помню, главно можно увидеть что там за update или insert выполняется и передаваемые аргументы там тоже вроде можно было увидеть.

anonymous
()

Интересно то, что если выполнять пошагово в отладчике, в БД всё прекрасно сохраняется и поле NumberNoticeFk не NULL, а соответствующий ID.

Кстати, а откуда берутся объекты, не из другого ли потока?

Deleted
()

перед commit() сущность имеет ссылку на родителя (NoticeDraft на NumberNotice), а в БД ничего нема?

Как проверяешь? Если ты проверяешь из другого потока или просто вне контекста транзакции, то естественно у тебя данных в базе нет.

hippi90 ★★★★★
()
Ответ на: комментарий от anonymous

Меня тоже это сбивает с толку, однако это legacy код, который я лишь поддерживаю.

Потихоньку привык, но отрефакторить пока не могу: рога пообломают за такое.

Deleted
()
Ответ на: комментарий от Deleted

Хуже: получаются через JPA, маппятся (каким-то самописным маппером, написанным достаточно давно, во времена Java 5|6) на DTO, сериализуются, отправляются на клиент в DXT/GWT, потом возвращаются на сервак и только после этого выполняется merge(). Честно скажу: я не знаю, каким чудом это работает.

Маппер вырезает все ссылки и поля, кроме id у ссылочных объектов. Но передается один хрен, как объект.

Deleted
()
Ответ на: комментарий от hippi90

Сижу, наблюдаю в отладке за значениями в MySQL Workbench (SELECT'ами). Данные появляются после commit (но только если идет отладка, в релизной сборке ничего нема), вроде. Но в отладчике у объекта ссылки остаются.

Неправильно сформулировал. В отладке - данные есть до коммита, в БД - после, и только в отладке.

Deleted
()
Ответ на: комментарий от Deleted

Посмотри как включить логирование hibernate для отображения запросов и аргументов (помню там отдельно включалось). Может так случится что у тебя выполняются два update над одной записью, один за другим.

anonymous
()
Ответ на: комментарий от anonymous

Понял. Знаю как, примерно. Проверю, спасибо.

Deleted
()
Ответ на: комментарий от Deleted

А вообще: а что я ждал, интересно от операции merge с ланными, пришедшими «неизвестно откуда»?

Deleted
()
Ответ на: комментарий от anonymous

Включил show_sql.

Лучше бы не включал. Там экранов 15-20 SELECT'ов на 4 изменённые сущности (приведенный выше кусок кода).

Наверно надо что-то другое включать, так ничего не понятно.

Deleted
()
Ответ на: комментарий от Deleted

Знаешь, что самое смешное:

Сократил метод до:

em.getTransaction().begin();
final DraftNotice draft = entityManager.find(DraftNotice.class, id); // тут вместо id установил литерал с заранее известной сущностью в подготовленной БД
draft.setMutatedBy("helloworld");
em.merge(draft); // тестировал и без этой строчки, так как сущность не detached
em.getTransaction().commit();

Но обновлять не хочет. Вот вообще не трогает ничего.

Но! Выполняю пошагово в отладчике - все данные появляются.

Как же у меня горит.

Deleted
()
Последнее исправление: merhalak (всего исправлений: 3)
Ответ на: комментарий от Deleted

DraftNotice покажи

вообще, при таких глюках, я содавал отдельный временный test с минимальным окружением и проверял на воспроизводимость в нём.

Хотя, вот этот вот фокус, когда работает только в отладчике, похож на типичные проблемы многопоточности.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Deleted

Сегодня уже не могу.

А в чем может заключаться типичная проблема? Одновременный доступ к сущности без версионирования (да, там нет версий, насколько помню, точнее могу только завтра).

Deleted
()
Ответ на: комментарий от Deleted

http://www.baeldung.com/hibernate-save-persist-update-merge-saveorupdate «if the entity is persistent, then this method call does not have effect on it (but the cascading still takes place).» Там сказано что merge не будет работать на persistent объектах, т.е. без ошибок, просто ничего не сделает. У тебя сущности detached? Т.е. ты их не вытащил через entity manager а потом пытаешься уже мержем засунуть обратно?

anonymous
()
Ответ на: комментарий от Deleted

А в чем может заключаться типичная проблема? Одновременный доступ к сущности без версионирования

Неа, просто конкурентный доступ к неким ресурсам, не защищённым блокировками.

Сущность, то в последнем примере никак другой поток не мог записывать. А вот юзать EntityManager из разных потоков - вполне.

Deleted
()
Ответ на: комментарий от anonymous

Считай, я их вытащил через EntityManager, выполнил detach(), обрубил лишние ссылки на уровне 2 (кроме id), сериализовал при пересылке через GWT RPC, потом обратная пересылка, потом уже merge().

Версионирования сущностей у меня нема.

Deleted
()
Последнее исправление: merhalak (всего исправлений: 1)
Ответ на: комментарий от Deleted

Да, если один инстанс менегера одновременно используется в разных потоках.

зы. одновременно или нет - не имеет значения.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Deleted

Судя по всему да, так и есть.

Проблема в том, что я плохо знаком с DI вообще и Guice в частности.

Сделал отдельный persistance-unit, вручную сделал EntityManagerFactory (без инъекций зависимостей) и создал EntityManager: всё работает без нареканий.

Вроде написано всё было корректно:
@Singleton (Guice) для сервлета,
@Inject (Guice) для Provider<EntityManager>,
однако работать корректно не желает.

Придётся разбираться с Google Guice подробно.

Deleted
()
Ответ на: комментарий от Deleted

Provider<EntityManager>,

Так по логике провайдер унутри должен делать что-то вроде

 
public EntityManager get() {
  return entityManagerFactory.createEntityManager(); 
}

Если он выглядит иначе, то надо задуматься (правда нафиг провайдер, если можно фабрику инжектить). Опять же, результат вызова провайдера нельзя кешировать в полях класса-юзера.

Deleted
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.