I'm new to Spring/JPA/Hibernate, and while it sounds easy reality just hasn't been. I could use some help.
I have a parent Entity that holds a list of child Entities. I'll use these to keep the discussion simple:
@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> {};
Round 1, I create a new parent and a new child, add the child to the parent's list and set the parent on the child. When I save the parent the child is saved as well.
@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);
}
Now, Round 2, I add a new child:
@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);
}
However, this time the new child is never persisted!
I've tried most every variation of CascadeType, @GeneratedValue GenerationType, @Transactional Propagation type...
Tracing this through hibernate (painful!), here's what I've found:
- When saving the second time, the problem is with the second (new) child.
- The issue seems to be that when it comes time to persist the parent's Child list
the new child is not in the EntityManager (yet) and thus in considered to be Transient.
- As a result, it is effectively being passed down the chain as null, resulting in the following:
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)
...
- It might be relevant that in my actual code "Child" also has a map of child Entities. This "value" is what gets passed down as null due to the "Transient" misappropriation.
- I've been using repository.saveAndFlush() to keep things synchronous for debugging. When I use just .save() my @PreUpdate EntityListener is called but the @PostUpdate listener never is.
- It seems that there wouldn't be a problem if Child were just persisted or given an Id at least before persisting Parent. But it also seems counter-productive to do that manually. Still, that's the only alternative I can think of.
Thanks for reading. Any help would be much appreciated!