Spring-JPA: updating parent Entity fails to persist new child Entities, interpreting them as Transient instead

10,073

Solution 1

I found the problem, though I don't really have a complete solution yet.

First, some additional background. In addition to Parent and Child, I had a related class I'll call "House" here. House has an EntityListener defined so that when it is saved/updated, the associated Parent & Child objects get created/updated. So it is during House's PostPersist/PostUpdate that Parent and Child objects are created, linked, pointed back to House, and then persisted.

So the problem seems to be this is done before the House transaction completes. By merely pulling out the Parent/Child activity until after the House activity completes, all the problems went away.

The only thing I can figure (I'll dig a little deeper) is that since House hasn't been completely persisted at that moment, it results in the Transient condition described above.

Update:

Chalk one up to ignorance. Apparently EntityCallback methods "should not call EntityMan­ager or Query methods and should not access any other entity objects." Did not know that. This raises the question now of how should I trigger an Entity's creation on another's activity. But I'll start another thread for that if necessary. Thanks all!

Solution 2

Everything in what you've shown seems pretty normal, so the problem might lie in that map you mentioned. I have a working example of a bidirectional one-to-many using Spring Data JPA on Github. You can look through the code or clone and run it with:

git clone git://github.com/zzantozz/testbed tmp
cd tmp/spring-data
mvn -q compile exec:java -D exec.mainClass=rds.springdata.JpaBiDiOneToManyExample
Share:
10,073
Didjit
Author by

Didjit

Updated on June 16, 2022

Comments

  • Didjit
    Didjit almost 2 years

    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!

  • Didjit
    Didjit about 11 years
    Thanks, Ryan. I'll take a look and get back to you.
  • Didjit
    Didjit about 11 years
    Ok, Ryan. I made sure my classes match yours and still had problems. But if I removed all "mechanisms" and just manually created, added, and saved - my objects worked fine. See my answer below about what I did discover. And thanks again for the work you put into making the sample code. It was very much appreciated! Unfortunately, I'm such a newb here that I can't +1 you, but it's there in spirit!
  • Ryan Stewart
    Ryan Stewart about 11 years
    +1: Nice job digging out the answer. Generally speaking, entity relationships like that are logical ones and should thus be modeled in your code where all the other logic lives. Trying to do it in a listener is rather similar to trying to write a database trigger to do the same thing. You wouldn't want that logic in the database, so why would you want to build it into the persistence framework?