Spring-JPA: при обновлении родительского объекта не удается сохранить новые дочерние объекты, вместо этого интерпретируя их как переходные

Я новичок в Spring/JPA/Hibernate, и, хотя это звучит просто, на самом деле это не так. Мне не помешала бы помощь.

У меня есть родительский объект, который содержит список дочерних объектов. Я буду использовать их, чтобы упростить обсуждение:

@Entity
public class Parent {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @OneToMany(fetch=FetchType.EAGER, cascade = CascadeType.ALL, mappedBy="parent")
    private List<Child> children= new ArrayList<Child>();

    etc...
}

@Entity
public class Child {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    private Parent parent;

    etc...
}

@Repository
public interface ParentRepository extends JpaRepository<Parent, Long> {};

Раунд 1, я создаю нового родителя и нового дочернего элемента, добавляю дочерний элемент в список родителя и устанавливаю родителя для дочернего элемента. Когда я сохраняю родителя, ребенок также сохраняется.

@Transactional(propagation=Propagation.REQUIRES_NEW)
void create() {
    Parent parent = new Parent();
    Child  child  = new Child();
    parent.add(child);
    child.setParent(parent);
    parent = repository.save(parent);
}

Теперь, раунд 2, я добавляю нового ребенка:

@Transactional(propagation=Propagation.REQUIRES_NEW)
void update() {
    Parent parent = repository.findOne(parentID);
    Child newChild = new Child();
    newChild.setParent(parent);
    parent.add(newChild);
    parent = repository.save(parent);
}

Однако на этот раз новый ребенок никогда не сохраняется!

Я пробовал почти все варианты CascadeType, @GeneratedValue GenerationType, @Transactional Propagation type...

Проследив это через спящий режим (мучительно!), вот что я нашел:

  • При сохранении во второй раз проблема со вторым (новым) потомком.
  • Проблема, по-видимому, заключается в том, что когда приходит время сохранять родительский список дочерних элементов, новый дочерний элемент отсутствует в EntityManager (пока) и, следовательно, считается переходным.
  • В результате он фактически передается по цепочке как null, что приводит к следующему:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is
javax.persistence.RollbackException: Error while committing thetransaction
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
...

Caused by: javax.persistence.RollbackException: Error while committing the transaction
    at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:92)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:512)
...

Caused by: org.hibernate.AssertionFailure: collection [null] was not processed by flush()
    at org.hibernate.engine.spi.CollectionEntry.postFlush(CollectionEntry.java:225)
...
  • Может быть важно, что в моем фактическом коде «Ребенок» также имеет карту дочерних сущностей. Это «значение» — это то, что передается как null из-за незаконного присвоения «Transient».
  • Я использовал репозиторий.saveAndFlush(), чтобы синхронизировать вещи для отладки. Когда я использую только .save(), вызывается мой @PreUpdate EntityListener, но слушатель @PostUpdate никогда не вызывается.
  • Кажется, что не было бы проблемы, если бы Child был просто сохранен или ему был присвоен идентификатор, по крайней мере, до сохранения Parent. Но также кажется контрпродуктивным делать это вручную. Тем не менее, это единственная альтернатива, о которой я могу думать.

Спасибо за прочтение. Любая помощь приветствуется!


person Didjit    schedule 26.02.2013    source источник
comment
К сожалению, мы не видим в вашей трассировке стека, какая коллекция имеет значение null. Может ли быть так, что карта не инициализирована у вашего ребенка? Не могли бы вы попробовать назначить новую хэш-карту атрибуту поля напрямую, чтобы проверить, устраняет ли это ошибку? У Hibernate есть некоторые причуды с нулевыми коллекциями. Попробуйте убедиться, что коллекции не являются нулевыми, и перезапустите тест.   -  person Martin Frey    schedule 26.02.2013
comment
Спасибо за ответ, Мартин. Нуль - это дочерний элемент. В объекте это карта, в которой есть объекты. Однако, когда родитель сохраняется, Hibernate просматривает дочерний элемент, не видит его в EntityManager, решает, что он должен быть переходным, и поэтому просто устанавливает его значение равным нулю (в карте идентификаторов, а не в исходном объекте). Есть ли у Hibernate проблемы с картами, в частности?   -  person Didjit    schedule 26.02.2013
comment
Спящий режим, имеющий проблему с коллекциями, было бы слишком много, чтобы сказать. Но с коллекциями может быть сложно справиться. У нас возникли проблемы с удалением потерянных файлов (=true), когда мы полностью заменили некоторые коллекции. К счастью для нас, эти вещи обычно описываются людьми и документацией довольно хорошо, и мы решили это быстро, как только вы знаете, что искать. По сути, вы не должны повторно инициализировать коллекции, когда они уже существуют, а вместо этого использовать очистку. Вот почему у меня была эта идея для вашей проблемы. Проще всего создать тестовый пример с A, B и C и запустить его, чтобы определить вашу проблему.   -  person Martin Frey    schedule 27.02.2013
comment
Да. Не видел, чтобы ты уже решил это. Так что забудьте о моем комментарии выше о тестовом примере :) Это правда, что вы не должны вносить изменения/запросы в entitylistener. Я не считаю это плохой практикой (триггер ak db), но это может быть трудно контролировать. Основная проблема здесь заключается в том, что ActionQueue Hibernate уже построен или находится в стадии создания.   -  person Martin Frey    schedule 27.02.2013
comment
С другой стороны, Envers делает именно это, но они управляют дополнительной работой в так называемых воркерах, прикрепленных к событию «до завершения транзакции». Вместо того, чтобы вносить изменения напрямую, вы создаете другую очередь действий для обработки. Мы построили систему электронной почты так же, как envers, и она работает, как и ожидалось.   -  person Martin Frey    schedule 27.02.2013
comment
Некоторое время назад я смотрел на Энверса. Это выглядело идеально, за исключением того, что у нас есть некоторые пользовательские потребности, которые все равно придется создавать. Тем не менее, это выглядело красиво.   -  person Didjit    schedule 27.02.2013


Ответы (2)


Я нашел проблему, хотя у меня еще нет полного решения.

Во-первых, некоторая дополнительная предыстория. В дополнение к Parent и Child у меня был родственный класс, который я назову здесь «House». В House определен EntityListener, поэтому при его сохранении/обновлении связанные объекты Parent и Child создаются/обновляются. Таким образом, во время PostPersist/PostUpdate House объекты Parent и Child создаются, связываются, указывают обратно на House, а затем сохраняются.

Таким образом, проблема, похоже, заключается в том, что это делается до завершения транзакции House. Просто вытащив активность «Родитель/Ребенок» до тех пор, пока не завершится действие «Дом», все проблемы исчезнут.

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

Обновление:

Спишите одно на невежество. По-видимому, методы EntityCallback "не должны вызывать методы EntityManager или Query и не должны обращаться к каким-либо другим объектам сущностей. ." Не знал этого. Это поднимает вопрос о том, как следует инициировать создание объекта при чужой активности. Но я начну другую тему для этого, если это необходимо. Спасибо всем!

person Didjit    schedule 26.02.2013
comment
+1: Хорошая работа по поиску ответа. Вообще говоря, отношения сущностей, подобные этому, являются логическими и поэтому должны быть смоделированы в вашем коде, где живет вся остальная логика. Попытка сделать это в слушателе очень похожа на попытку написать триггер базы данных, чтобы сделать то же самое. Вам не нужна эта логика в базе данных, так зачем вам встраивать ее в структуру постоянства? - person Ryan Stewart; 27.02.2013

Все, что вы показали, кажется вполне нормальным, поэтому проблема может заключаться в той карте, которую вы упомянули. У меня есть рабочий пример двунаправленного соединения «один ко многим» с использованием Spring Data JPA на Github. Вы можете просмотреть код или клонировать и запустить его с помощью:

git clone git://github.com/zzantozz/testbed tmp
cd tmp/spring-data
mvn -q compile exec:java -D exec.mainClass=rds.springdata.JpaBiDiOneToManyExample
person Ryan Stewart    schedule 26.02.2013
comment
Спасибо, Райан. Я посмотрю и вернусь к вам. - person Didjit; 26.02.2013
comment
Хорошо, Райан. Я убедился, что мои классы совпадают с вашими, и у меня все еще были проблемы. Но если я убрал все механизмы и просто вручную создавал, добавлял и сохранял - мои объекты работали нормально. Смотрите мой ответ ниже о том, что я действительно обнаружил. И еще раз спасибо за работу, которую вы вложили в создание примера кода. Это очень ценилось! К сожалению, я здесь такой новичок, что не могу поставить вам +1, но по духу это там! - person Didjit; 27.02.2013