tags:

views:

229

answers:

2

I have seen posts all over the internet that talk about how to fix the TransientObjectExceptions during save/update/delete but I am having this problem when calling list on my Criteria.

I have two objects A and B. A has a field named b which is of type B. In my mapping b is mapped as a many-to-one. This all runs in a larger persistence framework (the framework is kind of like Core Data) and so I don't use any cascades in my hibernate mappings since cascades are handled at a higher level.

This is the interesting code surrounding my criteria:

A a = new A();
B b = new B();
a.setB(b);

session.save("B", b); // Actually handled by the higher level
session.save("A", a); // framework, this is just for clarity

// transaction committed and session closed
...
// new session opened

Criteria criteria = session.createCriteria(A.class);
criteria.add(Restrictions.eq("b", b));
List<?> objects = criteria.list();

Basically I am looking for all objects of type A such that A.b equals a particular instance of b (I actually tried restructuring a query so that I was passing in the id of b just to make sure that b wasn't causing me problems).

Here is the stack trace that occurs when I call criteria.list():

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: B
at org.hibernate.engine.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:244)
at org.hibernate.type.EntityType.getIdentifier(EntityType.java:449)
at org.hibernate.type.ManyToOneType.nullSafeSet(ManyToOneType.java:141)
at org.hibernate.loader.Loader.bindPositionalParameters(Loader.java:1769)
at org.hibernate.loader.Loader.bindParameterValues(Loader.java:1740)
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1612)
at org.hibernate.loader.Loader.doQuery(Loader.java:717)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:270)
at org.hibernate.loader.Loader.doList(Loader.java:2294)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2172)
at org.hibernate.loader.Loader.list(Loader.java:2167)
at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:119)
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1706)
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:347)

Here is my mapping:

<class entity-name="A" lazy="false">
<tuplizer entity-mode="dynamic-map" class="MyTuplizer" />
<id type="long" column="id">
<generator class="native" />
</id>
<many-to-one name="b" entity-name="B" column="b_id" lazy="false" />
</class>

<class entity-name="B" lazy="false">
<tuplizer entity-mode="dynamic-map" class="MyTuplizer" />
<id type="long" column="id">
<generator class="native" />
</id>
</class>

Can anyone help me figure out why I would be getting a TransientObjectException during a fetch? Preferably I would like to find a solution that does not rely on cascades since they tend to mask problems that occur in the higher level framework.

+1  A: 

The problem is that b was made persistent in another session, which is closed and the query is created in a new session. When a session is closed, all objects in its persistence context become detached. If you want to later reuse them in another session, you need to re-attach them to that session first:

session.update(b);

Quote from the Hibernate book:

The update() operation on the Session reattaches the detached object to the persistence context and schedules an SQL UDPATE. Hibernate must assume that the client modified the object while it was detached. (Otherwise, if you’re certain that it hasn’t been modified, a lock() would be sufficient.) The persistence context is flushed automatically when the second transaction in the conversation commits, and any modifications to the once detached and now persistent object are synchronized with the database.

The saveOrUpdate() method is in practice more useful than update(), save(), or lock(): In complex conversations, you don’t know if the item is in detached state or if it’s new and transient and must be saved. The automatic state-detection provided by saveOrUpdate() becomes even more useful when you not only work with single instances, but also want to reattach or persist a network of connected objects and apply cascading options.

Note that there is also a merge() method, for cases when the same entity has been loaded into the new persistence context before the older detached instance could be re-attached. In this case, you have two physically distinct instances representing the same entity, thus they should be merged to avoid a NonUniqueObjectException.

Péter Török
Again thanks Peter! I had been pounding my head on this for days until you pointed this out.
rancidfishbreath
A: 

Another easy way accomplish the same is to use the attribute cascade=all on the collection mapping of child class from within the parent class mapping. Here's how the mapping looks

<class entity-name="A" lazy="false">
<tuplizer entity-mode="dynamic-map" class="MyTuplizer" />
<id type="long" column="id">
<generator class="native" />
</id>
<many-to-one name="b" entity-name="B" column="b_id" lazy="false" cascade="all" />
</class>

<class entity-name="B" lazy="false">
<tuplizer entity-mode="dynamic-map" class="MyTuplizer" />
<id type="long" column="id">
<generator class="native" />
</id>
</class>
Amit Sharma
Except that I explicitly stated in the question that I do not want to use cascades for the reasons mentioned in the question.
rancidfishbreath
I agree and respect your explanation at the top. But cause this blog search-able over net, I wanted to suggest a different scenario and when cascades are not to be handled by code, it can be done this way. Will modify my part as well.
Amit Sharma