-->
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.  [ 11 posts ] 
Author Message
 Post subject: NonUniqueObjectException Misunderstanding
PostPosted: Wed Aug 05, 2009 2:09 am 
Newbie

Joined: Mon Feb 23, 2009 9:08 am
Posts: 9
Hi all,

Let me start by saying that I am relantively new to using Hibernate more so Hibernate annotations. In saying that as most developers have experienced I've have had to work things out by myself through trial and error and little home projects - however there is only some much one can learn before we have to start asking around, for me, that misunderstanding is around the Hibernate exception of NonUniqueObjectException. The scenario is as follows:

I have 2 classes InvestmentTransaction.java and InitialFeeTransaction.java:

Code:
@Entity
@MappedSuperclass
public class InvestmentTransaction extends ProductTransaction {

   @OneToOne
   @Cascade( {org.hibernate.annotations.CascadeType.ALL})
   @JoinColumn(name = "ClientPaymentId")
   private InitialFeeTransaction initialFeeTransaction
        ...


Code:
@Entity
@DiscriminatorValue(value = "UFB")
public final class InitialFeeTransaction extends ProductTransaction {

   @OneToOne
   @Cascade( {org.hibernate.annotations.CascadeType.ALL})
   @JoinColumn(name = "ClientPaymentId")
   private InvestmentTransaction investmentTransaction;
        ...


As you can see from the class structure that an InvestmentTransaction can have a InitialFeeTransaction and vice versa. The reason behind this is when we save/update/delete and investment transaction we want to update the initial fee transaction and vice versa.

Subsequently my question is as follows:
After implementing the classes I noticed I can save/update/delete perfectly without any exceptions then I implemented transaction managment on the DAOs and the Services that access these classes:

applicationContext-dao.xml
Code:
<bean id="productTransactionDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
      <property name="transactionManager" ref="transactionManager" />
      <property name="transactionAttributes">
         <props>
            <prop key="save*">PROPAGATION_REQUIRED</prop>
            <prop key="delete*">PROPAGATION_REQUIRED</prop>
         </props>
      </property>
      <property name="target">
         <bean id="productTransactionDaoImpl" class="za.co.aforbes.fpc.db.dao.client.impl.hibernate.ProductTransactionDaoImpl">
            <property name="sessionFactory">
               <ref bean="ifaSessionFactory" />
            </property>
         </bean>
      </property>
   </bean>


and the applicationContext-service:
Code:
   <bean id="productTransactionServiceTransaction" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
      <property name="transactionManager" ref="transactionManager" />
      <property name="transactionAttributes">
         <props>
            <prop key="save*">PROPAGATION_REQUIRED</prop>
            <prop key="delete*">PROPAGATION_REQUIRED</prop>
         </props>
      </property>
      <property name="target">
         <ref bean="productTransactionService" />
      </property>
   </bean>


Now if I used the service to save the record, the records are saved successfully within a transaction without any exceptions. HOWEVER, if I delete an investment transaction I get the following exception:
Code:
Caused by: org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [za.co.aforbes.fpc.db.model.client.InitialFeeTransaction#9137144]
   at org.hibernate.engine.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:613)
   at org.hibernate.event.def.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:121)
   at org.hibernate.event.def.DefaultDeleteEventListener.onDelete(DefaultDeleteEventListener.java:74)
   at org.hibernate.impl.SessionImpl.fireDelete(SessionImpl.java:794)
   at org.hibernate.impl.SessionImpl.delete(SessionImpl.java:772)
   at org.springframework.orm.hibernate3.HibernateTemplate$25.doInHibernate(HibernateTemplate.java:852)
   at org.springframework.orm.hibernate3.HibernateTemplate.doExecute(HibernateTemplate.java:419)


So after some messing around I found out that if I don't reference the the classes and manually handle saving/updating/deleting then this resolves this problem - but i feel this is incorrect and makes life that much harder as now i have to remember when i save an investment tranascation go save the initial fee transaction... problematic and a maintenance nightmare?!

I also noticed if i comment out the transaction property for the service:
Code:
<!-- <prop key="delete*">PROPAGATION_REQUIRED</prop>  -->


That also resolves the issue - BUT i want transaction management - because if something fails in the delete method i want to rollback all actions.

Any feedback would be appreciated.

Thanks Justin


Top
 Profile  
 
 Post subject: Re: NonUniqueObjectException Misunderstanding
PostPosted: Wed Aug 05, 2009 5:29 pm 
Beginner
Beginner

Joined: Wed Jul 29, 2009 3:43 pm
Posts: 22
NonUniqueObjectException happens when you have two different objects (objects with different java identity) representing the same row in the database table(objects have the same value of the property mapped as primary key). Hibernate does not know how to resolve this conflict so it throws NonUniqueObjectException.

If the two differnt objects representing the same row in the database table were loaded in the same session then they will have the same java identity as well but if they were loaded in two different sessions then their java identity will be different.


Top
 Profile  
 
 Post subject: Re: NonUniqueObjectException Misunderstanding
PostPosted: Thu Aug 06, 2009 12:29 am 
Newbie

Joined: Mon Feb 23, 2009 9:08 am
Posts: 9
Thanks for the reply - more so what are some strategies to try and debug and workout where the object is loaded in two different sessions?


Top
 Profile  
 
 Post subject: Re: NonUniqueObjectException Misunderstanding
PostPosted: Thu Aug 06, 2009 3:53 am 
Beginner
Beginner

Joined: Wed Jul 29, 2009 3:43 pm
Posts: 22
You will need to debug your ProductTransactionDaoImpl(presumably that's the one you use to load these objects).
Can you post the code from ProductTransactionDaoImpl?


Top
 Profile  
 
 Post subject: Re: NonUniqueObjectException Misunderstanding
PostPosted: Thu Aug 06, 2009 4:17 am 
Beginner
Beginner

Joined: Wed Jul 29, 2009 3:43 pm
Posts: 22
Perhaps the easiest way is to put a breakpoint in the method that's doing the save/update and look at all the instances of InitialFeeTransaction (both as the main entity and as an association in the object graph of other entities). If you see two InitialFeeTransaction objects with different java identity (different memory addresses) but same value of the primary key then you will know that they were loaded in two different sessions.


Top
 Profile  
 
 Post subject: Re: NonUniqueObjectException Misunderstanding
PostPosted: Fri Aug 07, 2009 12:27 am 
Newbie

Joined: Mon Feb 23, 2009 9:08 am
Posts: 9
Thanks for the reply, but I really don't see why this is still happening? I mean I use a service "getProductTransactions()" -> which calls the dao "findProductTransactionByProduct(...)" -> which then uses Spring HibernateTemplate to do a "findAll"... pretty straight forward - not rocket science.

All this yields a collection of product transactions and displayed on the view. Then the user simply selects the product transactions and selects "Delete" of which the service "delete" method is called which then calls "delete" on the dao then bang... NonUniqueObjectException.

So basically the execution flow is service.find -> dao.find -> open session -> execute sql -> close session -> return collection to dao.find - return collection to service.find

Then the delete is service.delete -> dao.delete -> open session -> NonUniqueObjectException.

Now all your suggestions are valid but I don't see how they relate to this scenario as:
1. I use Spring hibernate template of which a lot of hibernate interactions is hidden
2. I use the same methodology service -> dao -> db for the other models in the application as no problems. The problem comes in when 1 model references another model i.e.: invesment transactions references a product transaction and vice versa.


Last edited by jjrun1 on Fri Aug 07, 2009 12:48 am, edited 1 time in total.

Top
 Profile  
 
 Post subject: Re: NonUniqueObjectException Misunderstanding
PostPosted: Fri Aug 07, 2009 12:46 am 
Newbie

Joined: Mon Feb 23, 2009 9:08 am
Posts: 9
Class structure:

Code:
public abstract class BaseHibernateDao<P extends Serializable, M extends BaseModel<P>> extends
      HibernateDaoSupport implements BaseDao<P,M> {
...
   @SuppressWarnings("unchecked")
   public Collection<M> findAll() {
      StringBuilder sql = new StringBuilder();
      sql.append("FROM " + baseModelClass.getCanonicalName());
      return getHibernateTemplate().find(sql.toString());
   }

   public final boolean save(M entity) {
      if(model.isNew()) {
         getHibernateTemplate().save(model);
      } else {
         getHibernateTemplate().saveOrUpdate(model);
      }
      return true;
   }
...
}


Code:
public final class ProductTransactionDaoImpl extends BaseHibernateDao<Long,ProductTransaction> implements
      ProductTransactionDao {
...
   @SuppressWarnings("unchecked")
   @Override
   public final Collection<ProductTransaction> findProductTransactionsByProduct(Product product) {
      if(product == null) {
         return new LinkedList<ProductTransaction>();
      }

      StringBuilder sql = new StringBuilder(100);
      sql.append("select distinct transaction ");
      sql.append("from " + ClassUtils.getShortName(ProductTransaction.class.getSimpleName())
            + " transaction ");
      sql.append("where transaction.productId = ? ");
      sql.append("order by transaction.date desc");

      Collection<ProductTransaction> data = getHibernateTemplate().find(sql.toString(),
            new Object[] {product.getPrimaryId()});

      if(CommonUtils.isEmpty(data)) {
         return null;
      }
      return data;
   }
...
}


Top
 Profile  
 
 Post subject: Re: NonUniqueObjectException Misunderstanding
PostPosted: Fri Aug 07, 2009 6:21 am 
Beginner
Beginner

Joined: Wed Jul 29, 2009 3:43 pm
Posts: 22
Are you sure that you are passing the same object to HibernateTemplate.delete() that you got from HibernateTemplate.find(). What I mean to ask is are you cloning/copying the contents of the object to create another object (before displaying them or afterwards) before passing them to HibernateTemplate.delete().

If you are using the same object that you got from find() then I suspect that the session that HibernateTemplate is using is the one that was used for some other request/thread.


Top
 Profile  
 
 Post subject: Re: NonUniqueObjectException Misunderstanding
PostPosted: Mon Aug 17, 2009 3:00 am 
Newbie

Joined: Mon Feb 23, 2009 9:08 am
Posts: 9
No I'm not cloning or copy the object. The objects are loaded into a collection from the service passed to the backing bean to display on the view. When the user makes a selection the selected row is used to get the selected model and passed to the service to delete... simple (so I thought).


Top
 Profile  
 
 Post subject:
PostPosted: Mon Aug 17, 2009 3:55 am 
Newbie

Joined: Tue Aug 04, 2009 6:46 am
Posts: 17
Hi all


Top
 Profile  
 
 Post subject: Re: NonUniqueObjectException Misunderstanding
PostPosted: Mon Aug 17, 2009 9:38 am 
Expert
Expert

Joined: Tue May 13, 2008 3:42 pm
Posts: 919
Location: Toronto & Ajax Ontario www.hibernatemadeeasy.com
Here's a little info I wrote on the topic. You might find it Helpful:

http://jpa.ezhibernate.com/Javacode/learn.jsp?tutorial=07howhibernateworks

Quote:
Object Comparisons

Once you start mixing and matching persistent and detached objects within your code, which pretty much any J2EE application will do at some point in time, you will find some not-so-funny, and potentially non-intuitive, problems that come up when you start doing comparisons of instances that you would think would be the same.

For example, take a look at the following code that creates two instances, user1 and user2, based on the same, identical, database record. What do you think the output of the code snippet would be?

Session session=HibernateUtil.beginTransaction();

User user1 = new User();
user1.setPassword("aaaaaa");
Long id = (Long)session.save(user1);
session.evict(user1);
User user2 = (User)session.get(User.class, id);


System.out.print("The instances are the same: ");
System.out.println( user1.equals(user2));

HibernateUtil.commitTransaction();
Since both instances of the User class are based on the same database record, they will have all of their properties set to the same values, which means the two objects are essentially the same, but the comparison of the two objects returns false. It's somewhat non-intuitive, but if you know what's going on under the covers of the Java Virtual Machine, it actually makes sense.

By default, when you compare two instances using .equals(), the compiler simply compares the memory locations of the two instances, as opposed to comparing their actual property values. Since we have two separate instances, we end up having two separate memory locations, and a .equals() comparison returns false. To overcome such situations, a Hibernate best practice is to have all of your JPA annotated classes properly override the inherited .hashcode() and .equals() methods, providing an implementation that makes sense for the class. That way, when two instances with exactly the same state are compared, the actual properties the object contains will be compared, and the compiler will not simply look at the memory locations of objects when performing an equality comparison.

The org.hibernate.NonUniqueObjectException

So, as we have seen, the following code snippet creates two instances, user1 and user2, both of which share the same set of properties, but with the main difference being the fact that user1 becomes a detached object after the evict(user1); method is called, whereas the instance user2 is a persistent object right up until the point that the transaction gets committed. Here's the code:

Session session=HibernateUtil.beginTransaction();
User user1 = new User();
user1.setPassword("aaaaaa");
Long id = (Long)session.save(user1);
session.evict(user1);
User user2 = (User)session.get(User.class, id);
HibernateUtil.commitTransaction();

Now, what do you think would happen if you changed some values in the detached instance, user1, and then tried to use the Hibernate Session to update that instance, considering the fact that user2, an instance that shares its id with user1, is already associated with the Hibernate Session? What would happen if you tried to run the following code:

Session session=HibernateUtil.beginTransaction();
User user1 = new User();
user1.setPassword("aaaaaa");
Long id = (Long)session.save(user1);
session.evict(user1);
User user2 = (User)session.get(User.class, id);
user1.setVerified(true);
session.saveOrUpdate(user1);
HibernateUtil.commitTransaction();

Well, here's another rule that Hibernate strictly enforces: only one instance of a class with a given unique primary key can be associated with the Session at a time. In this case, if we try to re-associate the evicted user1 with the Hibernate Session, Hibernate will kick out to us a NonUniqueObjectException, indicating that it is already managing an instance that represents that particular database record. So, Hibernate will gladly manage your unique instances, but fundamentally, it is that primary key that makes an object unique, and the developer must be careful not to add a second, non-unique instance of a class to an active Hibernate Session.


http://jpa.ezhibernate.com/Javacode/learn.jsp?tutorial=07howhibernateworks

_________________
Cameron McKenzie - Author of "Hibernate Made Easy" and "What is WebSphere?"
http://www.TheBookOnHibernate.com Check out my 'easy to follow' Hibernate & JPA Tutorials


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 11 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.