In an earlier post we saw how to reattach detached objects using update() and lock(). Consider the below code where we try to reattach an object.
The solution to above issue would be to
The other option would be to let Hibernate merge the two objects, without us worrying about it.public static void needForMerge() { Session session = sessionFactory.openSession(); System.out.println("Fetching the object via get call"); Entity entity1 = (Entity) session.get(Entity.class, 1); String data = entity1.getData(); session.close();//entity1 is detached System.out.println("data is " + data); entity1.setData("New data!!!");//detached object updated. hibernate not aware of change System.out.println("Opening a new session"); Session newSession = sessionFactory.openSession(); Transaction transaction = newSession.beginTransaction(); //loading the previous object from database Entity entity2 = (Entity) newSession.get(Entity.class, 1); System.out.println("The data now loaded from db is " + entity2.getData()); //trying to reattach the first object newSession.update(entity1); transaction.commit(); newSession.close(); }We would expect the code to make entity1 persistent again. However it throws an exception.
exception in thread "main" org.hibernate.NonUniqueObjectException: a different o bject with the same identifier value was already associated with the session: [c om.model.Entity#1] at org.hibernate.engine.StatefulPersistenceContext.checkUniqueness(StatefulPers istenceContext.java:590) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(Defau ltSaveOrUpdateEventListener.java:284) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(De faultSaveOrUpdateEventListener.java:223) at org.hibernate.event.def.DefaultUpdateEventListener.performSaveOrUpdate(Defau ltUpdateEventListener.java:33) at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(Defa ultSaveOrUpdateEventListener.java:70)The code fails because of the presence of an entity with same identifier in the persistence context. At any given time we can have only instance of a record associated with a session. In this entity2 is there in the session and we try to add entity1. As both are referring to the same record there is a failure.
The solution to above issue would be to
- Reattach entity1 to the second session
- Now try to load entity2
- As entity1 is already present in the persistence cache, Hibernate will simply return a reference to entity1
public static void mergeCase1() { Session session = sessionFactory.openSession(); System.out.println("Fetching the object via get call"); Entity entity1 = (Entity) session.get(Entity.class, 1); String data = entity1.getData(); session.close(); System.out.println("data is " + data); entity1.setData("mergeCase1"); Session newSession = sessionFactory.openSession(); Transaction transaction = newSession.beginTransaction(); Entity entity2 = (Entity) newSession.get(Entity.class, 1); System.out.println("The data now loaded from db is " + entity2.getData()); System.out.println("(entity1 == entity2) ? " + (entity1 == entity2)); Entity mergedEntity = (Entity) newSession.merge(entity1);
//it searches in pc, finds entity2 merges it with entity1 and returns entity2 System.out.println("(entity1 == mergedEntity) ? " + (mergedEntity == entity1)); System.out.println("(entity2 == mergedEntity) ? " + (mergedEntity == entity2));//entty1 is still detached transaction.commit(); //will cause an update to occur here newSession.close(); }In the above code
- We have first created a detached entity(entity1) and further even modified it.
- After that we have loaded a second entity (entity2).
- Now to avoid the previous exception we call upon the merge operation on entity1.
- The merge method returns the merged entity as another reference which we assigned to mergedEntity. The system displays the following on the console:
data is init
The data now loaded from db is mergeCase2 (entity1 == entity2) ? false 2797 [main] DEBUG org.hibernate.event.def.DefaultMergeEventListener - merging d etached instance ... (entity1 == mergedEntity) ? false (entity2 == mergedEntity) ? true ... 2860 [main] DEBUG org.hibernate.SQL - update Entity set DATA=? where id=?As can be seen from the above Hibernate did not modify the detached entity. Instead it searched for the entity with same identifier in the persistence cache. On finding a match (entity2) it merged the data in entity1 into entitity2 and returned the reference to the existing persistent object. Object entity1 is still detached but the changes have been merged into entity2 - the object in session's PersistentCache.
As the data had been modified, Hibernate also schedules an update query on the object, thus reflecting the changes made to entity1 when it was in the detached state. If entity1 had no changes then the update query will not be fired.
But what would happen id we merged entity1 without having entity2 in the cache ?
public static void mergeCase2() { Session session = sessionFactory.openSession(); System.out.println("Fetching the object via get call"); Entity entity1 = (Entity) session.get(Entity.class, 1); String data = entity1.getData(); session.close(); System.out.println("data is " + data); entity1.setData("mergeCase2"); Session newSession = sessionFactory.openSession(); Transaction transaction = newSession.beginTransaction(); Entity mergedEntity = (Entity) newSession.merge(entity1); //it searches in pc, not found -searches db - fetches dbentity, merges it with entity1 and returns dbentity System.out.println("(entity1 == mergedEntity) ? " + (mergedEntity == entity1)); transaction.commit(); //will cause an update to occur here newSession.close(); }The log displays the following:
data is init 2297 [main] DEBUG org.hibernate.event.def.AbstractSaveEventListener - detached instance of: com.model.Entity 2297 [main] DEBUG org.hibernate.event.def.DefaultMergeEventListener - merging d etached instance 2297 [main] DEBUG org.hibernate.SQL - select entity0_.id as id0_0_, entity0_.DATA as DATA0_0_ from Entity entity0_ where entity0_.id=? 2328 [main] DEBUG org.hibernate.loader.Loader - done entity load (entity1 == mergedEntity) ? false 2344 [main] DEBUG org.hibernate.pretty.Printer - com.model.Entity{id=1, data=me rgeCase2} 2360 [main] DEBUG org.hibernate.SQL - update Entity set DATA=? where id=?As can be seen from above, on call of merge
- Hibernate again checked its persistence cache. It did not find the object.
- So it executed a database select to retrieve the entity with id 1 and added it to its persistence cache.
- It then merged this new entity with the detached instance entity1 and detected the change in value for data. So after this it executed an update call ensuring that the change is persisted back to the database. In case of no change, the update query will not be executed.
public static void mergeCase3() { Entity entity1 = new Entity(); //new object created; in Transient state entity1.setData("mergeCase3"); System.out.println("Creating a transient object "); Session newSession = sessionFactory.openSession(); Transaction transaction = newSession.beginTransaction(); Entity mergedEntity = (Entity) newSession.merge(entity1);
//it searches in pc, will find no match, no match in db either // creates a new instance and schedules it for insertion,
//new entity merges it with entity1 and returns new entity System.out.println("(entity1 == mergedEntity) ? " + (mergedEntity == entity1)); transaction.commit(); //will cause an insert to occur here newSession.close(); }Here I actually created a new object and asked Hibernate to merge it. The logs are as below:
Creating a transient object 2219 [main] DEBUG org.hibernate.event.def.DefaultMergeEventListener - merging t ransient instance ... 2281 [main] DEBUG org.hibernate.SQL - insert into Entity (DATA) values (?) (entity1 == mergedEntity) ? falseOn calling merge,
- Hibernate sees that the record has no id - so it need not check in the persistence cache or the database.
- Instead it schedules an insert call for the record adding it to the table and also to its persistence cache. Note however, that even in this case the merge method returns a different object than the one passed to it.
- Interestingly if I had used an assigned generator type here, Hibernate actually checks if the record is there in the persistence cache. Not finding it, it fires a select query on the database:
select entity0_.id as id0_0_, entity0_.DATA as DATA0_0_ from ENTITY entity0_ where entity0_.id=? 1082 [main] DEBUG org.hibernate.type.IntegerType - binding '4' to parameter: 1 ... 1082 [main] DEBUG org.hibernate.loader.Loader - done processing result set (0 r ows) ... 1084 [main] DEBUG org.hibernate.event.def.DefaultMergeEventListener - merging t ransient instance
It is only after these steps that Hibernate goes ahead with the insert sql.
- Merge unlike update does not always fire an SQL update query. The entity will be updated only it has been modified.
- Merge unlike lock is capable of detecting changes made in the detached instance.
- If merge finds that the object is present in the first level cache, it will use the same record. If not found in the cache, it will fetch it from the database.
- If the record does not exist at all, merge will schedule an insert for this record.
- The object returned by merge is always different to the one passed to it. This allows user to continue working wit the detached instance ( and possibly merge again) or user can discard the detached reference and work with the merged object)
very good explaination with example . it helped me to understand what is exactly merge doing
ReplyDeleteGreat explanation! the hibernate documentation should really have this.
ReplyDeleteHigh Praise People! Thanks
ReplyDeletegreat explanation... best regards from venezuela
ReplyDeleteExcellent explanation. No words to applause. Superb...
ReplyDeleteWonderfully explained...
ReplyDeleteNice! Helped me!
ReplyDeleteThank you for this post! The only post which cleared my doubt between save() & merge()
ReplyDeleteThanks, nice post
ReplyDeleteVery good post. Finally got answers to why merge fires select queries. It would have been good if there was a version of merge that took the entities at their face value to prevent select queries which take a lot of time for complex data models.
ReplyDeleteexplanation was too good, but i need some real time use case of merge
ReplyDeleteGreat post. Thank you very much !
ReplyDelete