-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 
Author Message
 Post subject: TransientObjectException with `cascade="all, delete-orphan"`
PostPosted: Fri Feb 26, 2016 11:44 am 
Newbie

Joined: Thu Feb 25, 2016 2:20 pm
Posts: 2
I am having trouble saving a new object with Hibernate which contains a nested collection that is mapped in hibernate as having `cascade="all, delete-orphan"`. More specifically, if I save the grandparent object it fails with the stack trace included below.

What's especially frustrating is we've come across this problem a few times, but it doesn't consistently appear in all collections we persist, only some of them and we don't know Hibernate well enough to see the common cause. Any insight here is greatly appreciated.

In general I seem to frequently have to debug into Hibernate's source when trying to track down what's going on with errors, this seems strange for such a mature library. It's also a very slow process since the Hibernate internals can be quite complex to someone who's just trying to use it.

An abbreviated version of my object classes, hibernate mapping files and a repro case are included below the stack trace.

I've also made a StackOverflow Post about this problem.

Error w/ Stack Trace
Code:
    com.foo.server.services.db.exception.InvalidObjectException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.foo.server.model.house.conditioning.Duct
       at com.foo.server.services.db.Transaction.commit(Transaction.java:33)
       at com.foo.server.services.db.service.PersistenceManager.commitTransactionIfOpen(PersistenceManager.java:83)
       at com.foo.server.services.db.service.DataService.save(DataService.java:230)
       at com.foo.server.services.db.service.DataService.save(DataService.java:188)
       at com.foo.server.model.house.TestHousePlan_JDO.testAddDistributionSystemSave(TestHousePlan_JDO.java:55)
    Caused by: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.foo.server.model.house.conditioning.Duct
       at org.hibernate.engine.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:242)
       at org.hibernate.collection.AbstractPersistentCollection.getOrphans(AbstractPersistentCollection.java:919)
       at org.hibernate.collection.PersistentList.getOrphans(PersistentList.java:70)
       at org.hibernate.engine.CollectionEntry.getOrphans(CollectionEntry.java:373)
       at org.hibernate.engine.Cascade.deleteOrphans(Cascade.java:364)
       at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:348)
       at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:266)
       at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:243)
       at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:193)
       at org.hibernate.engine.Cascade.cascade(Cascade.java:154)
       at org.hibernate.event.def.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:154)
       at org.hibernate.event.def.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:145)
       at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:88)
       at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:49)
       at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1028)
       at com.foo.server.services.db.Transaction.commit(Transaction.java:28)
       ... 31 more


HousePlan.java
Code:
    public class HousePlan
    {
        Long id;
        List<DistributionSystem> distributionSystems = Lists.newArrayList();
        // Other fields excluded
   
        public HousePlan()
        {
            distributionSystems.add(new DistributionSystem());
        }
   
        // getters + setters
    }


HousePlan.hbm.xml
Code:
    <hibernate-mapping package="com.foo.server.model.house">
      <class name="HousePlan" table="house_plan">
        <id name="id" column="id">
          <generator class="native" />
        </id>
       
        <list name="distributionSystems" cascade="all,delete-orphan" lazy="false">
          <key column="housePlanId" not-null="false" />
          <list-index column="housePlanIndex" />
          <one-to-many class="com.foo.server.model.house.conditioning.DistributionSystem" />
        </list>
   
        <!-- Other mappings excluded -->
   
      </class>
    </hibernate-mapping>


DistributionSystem.java
Code:
    public class DistributionSystem
    {
        Long id;
        List<Duct> ducts = Lists.newArrayList();
        // Other fields excluded
   
        public DistributionSystem()
        {
            ducts.add(new Duct());
            ducts.add(new Duct());
        }
   
        // getters + setters excluded
    }   


DistributionSystem.hbm.xml
Code:
    <hibernate-mapping package="com.foo.server.model.house.conditioning">
      <class name="DistributionSystem" table="distribution_system">
        <id name="id" column="id">
          <generator class="native" />
        </id>
       
        <!-- Note the usage of `cascade="all,delete-orphan"` which is the normal solution to this problem -->
        <list name="ducts" cascade="all,delete-orphan" lazy="false">
          <key column="distributionSystemId" not-null="false" />
          <list-index column="distributionSystemIndex" />
          <one-to-many class="com.foo.server.model.house.conditioning.Duct" />
        </list>
   
        <!-- Other mappings excluded -->
   
      </class>
    </hibernate-mapping>     


Duct.java
Code:
    public class Duct
    {
        Long id;
        // Other fields excluded
   
        // Constructor excluded
   
        // getters + setters excluded
    }   


Duct.hbm.xml
Code:
    <hibernate-mapping package="com.foo.server.model.house.conditioning">
      <class name="Duct" table="duct">
        <id name="id" column="id">
          <generator class="native" />
        </id>
   
        <!-- Other mappings excluded -->
   
      </class>
    </hibernate-mapping>


Repro case
Includes abbreviated implementation of how we are using hibernate.
Code:
    public class ReproCase
    {
        public static Session session = getSessionFactory().openSession();
   
        public static void main(String... args)
        {
            HousePlan plan = new HousePlan();
   
            // This save succeeds
            plan = save(plan);
   
            plan.getDistributionSystems().add(new DistributionSystem());
   
            // This save fails
            plan = save(plan); 
        }
   
        public static <T> T save(T object)
        {
            Transaction transaction = new Transaction(session);
            transaction.begin();
   
            // In our full app our object inheritance hierarchy allows for the
            // call to getId()
   
            // Objects with null ids are transient. Objects with non-null ids are
            // not transient, but might be detached from the persistence context so
            // here we ensure the object is attached.
   
            if (object.getId() != null)
            {
                object = (T)session.merge(object);
            }
   
            try
            {
                session.saveOrUpdate(object);
            }
            catch (Exception e)
            {
                session.merge(object);
            }
   
   
            // This is where the TransientObjectException occurs
            session.flush();
   
            transaction.commit();
   
            return object;
        }
    }


Top
 Profile  
 
 Post subject: Re: TransientObjectException with `cascade="all, delete-orphan"`
PostPosted: Sat Feb 27, 2016 6:23 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1628
Location: Romania
It puzzles me why do you use the legacy mapping when JPA is available for almost 10 years now.
You haven't specified he Hibernate version in use.

The collections are unidirectional, right?
If they are bidirectional, you should set both sides of the association.

The mappings look fine, so I think you can only debug it through to see why is it failing.


Top
 Profile  
 
 Post subject: Re: TransientObjectException with `cascade="all, delete-orphan"`
PostPosted: Sat Feb 27, 2016 6:37 pm 
Newbie

Joined: Thu Feb 25, 2016 2:20 pm
Posts: 2
mihalcea_vlad wrote:
It puzzles me why do you use the legacy mapping when JPA is available for almost 10 years now.
You haven't specified he Hibernate version in use.

The choice to use legacy mapping over JPA was a decision made prior to me being part of the project. In any case I am responsible for maintaining it now. The project currently uses Hibernate 3.3.2.

mihalcea_vlad wrote:
The collections are unidirectional, right?

Correct.

mihalcea_vlad wrote:
The mappings look fine, so I think you can only debug it through to see why is it failing.

I've tried to dig into the Hibernate implementation to understand what's actually going wrong, but the internal complexity of the Hibernate codebase is proving an obstacle here. I've put a breakpoint at getEntityIdentifierIfNotUnsaved on the line that actually throws the exception as detailed in the stack trace but I'm still trying to figure out exactly what is supposed to be going on in that sequence of calls, but what seems to be happening is that somehow the still unsaved list of ducts (Which I believe get added to the persistence context by way of merge() being called on the modified detached house plan instance) is being checked for orphans, which doesn't make sense as the list being checked has never been saved, nor has its contents. This triggers the error when the defensive check in getEntityIdentifierIfNotUnsaved looks to confirm the entity whose identifier is being asked for isn't transient, which obviously fails because it hasn't been saved yet.

Though now that I think about it, since this is Hibernate's internal flush() implementation, my assumptions about what constraints of the transient/persistent lifecycle actually apply may not hold as entities might be in weird transitory states that are implementation details.

It's really tough to simplify further because we don't know what actually triggers the problem, despite having a consistent repro case.


Top
 Profile  
 
 Post subject: Re: TransientObjectException with `cascade="all, delete-orphan"`
PostPosted: Sun Feb 28, 2016 11:41 am 
Hibernate Team
Hibernate Team

Joined: Thu Sep 11, 2014 2:50 am
Posts: 1628
Location: Romania
Since this is a very old Hibernate version which is not maintained anymore, I think it's worth upgrading to 3.5.6 or 3.6.10.
There could be a bug that possibly got fixed so you should definitely give it a try.

Another suggestion is to use many-to-one associations with bidirectional inverse collections on the other side.
The many-to-one associations are much more efficient and maybe this issue doesn't replicate for this mapping.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.