Reputation: 67723
Hibernate has a handful of methods that, one way or another, takes your object and puts it into the database. What are the differences between them, when to use which, and why isn't there just one intelligent method that knows when to use what?
The methods that I have identified thus far are:
save()
update()
saveOrUpdate()
saveOrUpdateCopy()
merge()
persist()
Upvotes: 212
Views: 127732
Reputation: 154190
You should prefer the JPA methods most of the time, and the update
for batch processing tasks.
A JPA or Hibernate entity can be in one of the following four states:
The transition from one state to the other is done via the EntityManager or Session methods.
For instance, the JPA EntityManager
provides the following entity state transition methods.
The Hibernate Session
implements all the JPA EntityManager
methods and provides some additional entity state transition methods like save
, saveOrUpdate
and update
.
To change the state of an entity from Transient (New) to Managed (Persisted), we can use the persist
method offered by the JPA EntityManager
which is also inherited by the Hibernate Session
.
The
persist
method triggers aPersistEvent
which is handled by theDefaultPersistEventListener
Hibernate event listener.
Therefore, when executing the following test case:
doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
LOGGER.info(
"Persisting the Book entity with the id: {}",
book.getId()
);
});
Hibernate generates the following SQL statements:
CALL NEXT VALUE FOR hibernate_sequence
-- Persisting the Book entity with the id: 1
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
Notice that the id
is assigned prior to attaching the Book
entity to the current Persistence Context. This is needed because the managed entities are stored in a Map
structure where the key is formed by the entity type and its identifier and the value is the entity reference. This is the reason why the JPA EntityManager
and the Hibernate Session
are known as the First-Level Cache.
When calling persist
, the entity is only attached to the currently running Persistence Context, and the INSERT can be postponed until the flush
is called.
The only exception is the IDENTITY
which triggers the INSERT right away since that's the only way it can get the entity identifier. For this reason, Hibernate cannot batch inserts for entities using the IDENTITY
generator.
The Hibernate-specific save
method predates JPA and it's been available since the beginning of the Hibernate project.
The
save
method triggers aSaveOrUpdateEvent
which is handled by theDefaultSaveOrUpdateEventListener
Hibernate event listener. Therefore, thesave
method is equivalent to theupdate
andsaveOrUpdate
methods.
To see how the save
method works, consider the following test case:
doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
Session session = entityManager.unwrap(Session.class);
Long id = (Long) session.save(book);
LOGGER.info(
"Saving the Book entity with the id: {}",
id
);
});
When running the test case above, Hibernate generates the following SQL statements:
CALL NEXT VALUE FOR hibernate_sequence
-- Saving the Book entity with the id: 1
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
As you can see, the outcome is identical to the persist
method call. However, unlike persist
, the save
method returns the entity identifier.
The Hibernate-specific update
method is meant to bypass the dirty checking mechanism and force an entity update at the flush time.
The
update
method triggers aSaveOrUpdateEvent
which is handled by theDefaultSaveOrUpdateEventListener
Hibernate event listener. Therefore, theupdate
method is equivalent to thesave
andsaveOrUpdate
methods.
To see how the update
method works consider the following example which persists a Book
entity in one transaction, then it modifies it while the entity is in the detached state, and it forces the SQL UPDATE using the update
method call.
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
LOGGER.info("Modifying the Book entity");
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
When executing the test case above, Hibernate generates the following SQL statements:
CALL NEXT VALUE FOR hibernate_sequence
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
-- Modifying the Book entity
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
Notice that the UPDATE
is executed during the Persistence Context flush, right before commit, and that's why the Updating the Book entity
message is logged first.
@SelectBeforeUpdate
to avoid unnecessary updatesNow, the UPDATE is always going to be executed even if the entity was not changed while in the detached state. To prevent this, you can use the @SelectBeforeUpdate
Hibernate annotation which will trigger a SELECT
statement that fetched loaded state
which is then used by the dirty checking mechanism.
So, if we annotate the Book
entity with the @SelectBeforeUpdate
annotation:
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
And execute the following test case:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
});
Hibernate executes the following SQL statements:
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
SELECT
b.id,
b.author AS author2_0_,
b.isbn AS isbn3_0_,
b.title AS title4_0_
FROM
book b
WHERE
b.id = 1
Notice that, this time, there is no UPDATE
executed since the Hibernate dirty checking mechanism has detected that the entity was not modified.
The Hibernate-specific saveOrUpdate
method is just an alias for save
and update
.
The
saveOrUpdate
method triggers aSaveOrUpdateEvent
which is handled by theDefaultSaveOrUpdateEventListener
Hibernate event listener. Therefore, theupdate
method is equivalent to thesave
andsaveOrUpdate
methods.
Now, you can use saveOrUpdate
when you want to persist an entity or to force an UPDATE
as illustrated by the following example.
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(book);
return book;
});
_book.setTitle("High-Performance Java Persistence, 2nd edition");
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(_book);
});
NonUniqueObjectException
One problem that can occur with save
, update
, and saveOrUpdate
is if the Persistence Context already contains an entity reference with the same id and of the same type as in the following example:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
try {
doInJPA(entityManager -> {
Book book = entityManager.find(
Book.class,
_book.getId()
);
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(_book);
});
} catch (NonUniqueObjectException e) {
LOGGER.error(
"The Persistence Context cannot hold " +
"two representations of the same entity",
e
);
}
Now, when executing the test case above, Hibernate is going to throw a NonUniqueObjectException
because the second EntityManager
already contains a Book
entity with the same identifier as the one we pass to update
, and the Persistence Context cannot hold two representations of the same entity.
org.hibernate.NonUniqueObjectException:
A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)
To avoid the NonUniqueObjectException
, you need to use the merge
method offered by the JPA EntityManager
and inherited by the Hibernate Session
as well.
The merge
fetches a new entity snapshot from the database if there is no entity reference found in the Persistence Context, and it copies the state of the detached entity passed to the merge
method.
The
merge
method triggers aMergeEvent
which is handled by theDefaultMergeEventListener
Hibernate event listener.
To see how the merge
method works consider the following example which persists a Book
entity in one transaction, then it modifies it while the entity is in the detached state, and passes the detached entity to merge
in a subsequence Persistence Context.
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
LOGGER.info("Modifying the Book entity");
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
When running the test case above, Hibernate executed the following SQL statements:
INSERT INTO book (
author,
isbn,
title,
id
)
VALUES (
'Vlad Mihalcea',
'978-9730228236',
'High-Performance Java Persistence',
1
)
-- Modifying the Book entity
SELECT
b.id,
b.author AS author2_0_,
b.isbn AS isbn3_0_,
b.title AS title4_0_
FROM
book b
WHERE
b.id = 1
-- Merging the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
Notice that the entity reference returned by merge
is different than the detached one we passed to the merge
method.
Now, although you should prefer using JPA merge
when copying the detached entity state, the extra SELECT
can be problematic when executing a batch processing task.
For this reason, you should prefer using update
when you are sure that there is no entity reference already attached to the currently running Persistence Context and that the detached entity has been modified.
To persist an entity, you should use the JPA persist
method. To copy the detached entity state, merge
should be preferred. The update
method is useful for batch processing tasks only. The save
and saveOrUpdate
are just aliases to update
and you should not probably use them at all.
Some developers call save
even when the entity is already managed, but this is a mistake and triggers a redundant event since, for managed entities, the UPDATE is automatically handled at the Persistence context flush time.
Upvotes: 34
Reputation: 8377
See the Hibernate Forum for a explanation of the subtle differences between persist and save. It looks like the difference is the time the INSERT statement is ultimately executed. Since save does return the identifier, the INSERT statement has to be executed instantly regardless of the state of the transaction (which generally is a bad thing). Persist won't execute any statements outside of the currently running transaction just to assign the identifier. Save/Persist both work on transient instances, ie instances which have no identifier assigned yet and as such are not saved in the DB.
Update and Merge both work on detached instances, ie instances which have a corresponding entry in the DB but which are currently not attached to (or managed by) a Session. The difference between them are what happens to the instance which is passed to the function. update tries to reattach the instance, that means that there must be no other instance of the persistent entity attached to the Session right now, otherwise an exception is thrown. merge, however, just copies all values to a persistent instance in the Session (which will be loaded if it is not currently loaded). The input object is not changed. So merge is more general than update, but may use more resources.
Upvotes: 71
Reputation: 948
None of the answers above are complete. Although Leo Theobald answer looks nearest answer.
The basic point is how hibernate is dealing with states of entities and how it handles them when there is a state change. Everything must be seen with respect to flushes and commits as well, which everyone seems to have ignored completely.
NEVER USE THE SAVE METHOD of HIBERNATE. FORGET THAT IT EVEN EXISTS IN HIBERNATE!
Persist
As everyone explained, Persist basically transitions an entity from "Transient" state to "Managed" State. At this point, a slush or a commit can create an insert statement. But the entity will still remains in "Managed" state. That doesn't change with flush.
At this point, if you "Persist" again there will be no change. And there wont be any more saves if we try to persist a persisted entity.
The fun begins when we try to evict the entity.
An evict is a special function of Hibernate which will transition the entity from "Managed" to "Detached". We cannot call a persist on a detached entity. If we do that, then Hibernate raises an exception and entire transaction gets rolled back on commit.
Merge vs Update
These are 2 interesting functions doing different stuff when dealt in different ways. Both of them are trying to transition the entity from "Detached" state to "Managed" state. But doing it differently.
Understand a fact that Detached means kind of an "offline" state. and managed means "Online" state.
Observe the code below:
Session ses1 = sessionFactory.openSession();
Transaction tx1 = ses1.beginTransaction();
HibEntity entity = getHibEntity();
ses1.persist(entity);
ses1.evict(entity);
ses1.merge(entity);
ses1.delete(entity);
tx1.commit();
When you do this? What do you think will happen? If you said this will raise exception, then you are correct. This will raise exception because, merge has worked on entity object, which is detached state. But it doesn't alter the state of object.
Behind the scene, merge will raise a select query and basically returns a copy of entity which is in attached state. Observe the code below:
Session ses1 = sessionFactory.openSession();
Transaction tx1 = ses1.beginTransaction();
HibEntity entity = getHibEntity();
ses1.persist(entity);
ses1.evict(entity);
HibEntity copied = (HibEntity)ses1.merge(entity);
ses1.delete(copied);
tx1.commit();
The above sample works because merge has brought a new entity into the context which is in persisted state.
When applied with Update the same works fine because update doesn't actually bring a copy of entity like merge.
Session ses1 = sessionFactory.openSession();
Transaction tx1 = ses1.beginTransaction();
HibEntity entity = getHibEntity();
ses1.persist(entity);
ses1.evict(entity);
ses1.update(entity);
ses1.delete(entity);
tx1.commit();
At the same time in debug trace we can see that Update hasn't raised SQL query of select like merge.
delete
In the above example I used delete without talking about delete. Delete will basically transition the entity from managed state to "removed" state. And when flushed or commited will issue a delete command to store.
However it is possible to bring the entity back to "managed" state from "removed" state using the persist method.
Hope the above explanation clarified any doubts.
Upvotes: 0
Reputation: 924
I found a good example showing the differences between all hibernate save methods:
http://www.journaldev.com/3481/hibernate-session-merge-vs-update-save-saveorupdate-persist-example
In brief, according to the above link:
save()
persist()
saveOrUpdate()
Can be used with or without the transaction, and just like save(), if its used without the transaction, mapped entities wont be saved un;ess we flush the session.
Results into insert or update queries based on the provided data. If the data is present in the database, update query is executed.
update()
merge()
Also for practical examples of all these, please refer to the link I mentioned above, it shows examples for all these different methods.
Upvotes: 5
Reputation: 8587
Here's my understanding of the methods. Mainly these are based on the API though as I don't use all of these in practice.
saveOrUpdate Calls either save or update depending on some checks. E.g. if no identifier exists, save is called. Otherwise update is called.
save Persists an entity. Will assign an identifier if one doesn't exist. If one does, it's essentially doing an update. Returns the generated ID of the entity.
update Attempts to persist the entity using an existing identifier. If no identifier exists, I believe an exception is thrown.
saveOrUpdateCopy This is deprecated and should no longer be used. Instead there is...
merge Now this is where my knowledge starts to falter. The important thing here is the difference between transient, detached and persistent entities. For more info on the object states, take a look here. With save & update, you are dealing with persistent objects. They are linked to a Session so Hibernate knows what has changed. But when you have a transient object, there is no session involved. In these cases you need to use merge for updates and persist for saving.
persist As mentioned above, this is used on transient objects. It does not return the generated ID.
Upvotes: 123
Reputation: 834
This link explains in good manner :
http://www.stevideter.com/2008/12/07/saveorupdate-versus-merge-in-hibernate/
We all have those problems that we encounter just infrequently enough that when we see them again, we know we’ve solved this, but can’t remember how.
The NonUniqueObjectException thrown when using Session.saveOrUpdate() in Hibernate is one of mine. I’ll be adding new functionality to a complex application. All my unit tests work fine. Then in testing the UI, trying to save an object, I start getting an exception with the message “a different object with the same identifier value was already associated with the session.” Here’s some example code from Java Persistence with Hibernate.
Session session = sessionFactory1.openSession();
Transaction tx = session.beginTransaction();
Item item = (Item) session.get(Item.class, new Long(1234));
tx.commit();
session.close(); // end of first session, item is detached
item.getId(); // The database identity is "1234"
item.setDescription("my new description");
Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
Item item2 = (Item) session2.get(Item.class, new Long(1234));
session2.update(item); // Throws NonUniqueObjectException
tx2.commit();
session2.close();
To understand the cause of this exception, it’s important to understand detached objects and what happens when you call saveOrUpdate() (or just update()) on a detached object.
When we close an individual Hibernate Session, the persistent objects we are working with are detached. This means the data is still in the application’s memory, but Hibernate is no longer responsible for tracking changes to the objects.
If we then modify our detached object and want to update it, we have to reattach the object. During that reattachment process, Hibernate will check to see if there are any other copies of the same object. If it finds any, it has to tell us it doesn’t know what the “real” copy is any more. Perhaps other changes were made to those other copies that we expect to be saved, but Hibernate doesn’t know about them, because it wasn’t managing them at the time.
Rather than save possibly bad data, Hibernate tells us about the problem via the NonUniqueObjectException.
So what are we to do? In Hibernate 3, we have merge() (in Hibernate 2, use saveOrUpdateCopy()). This method will force Hibernate to copy any changes from other detached instances onto the instance you want to save, and thus merges all the changes in memory before the save.
Session session = sessionFactory1.openSession();
Transaction tx = session.beginTransaction();
Item item = (Item) session.get(Item.class, new Long(1234));
tx.commit();
session.close(); // end of first session, item is detached
item.getId(); // The database identity is "1234"
item.setDescription("my new description");
Session session2 = sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
Item item2 = (Item) session2.get(Item.class, new Long(1234));
Item item3 = session2.merge(item); // Success!
tx2.commit();
session2.close();
It’s important to note that merge returns a reference to the newly updated version of the instance. It isn’t reattaching item to the Session. If you test for instance equality (item == item3), you’ll find it returns false in this case. You will probably want to work with item3 from this point forward.
It’s also important to note that the Java Persistence API (JPA) doesn’t have a concept of detached and reattached objects, and uses EntityManager.persist() and EntityManager.merge().
I’ve found in general that when using Hibernate, saveOrUpdate() is usually sufficient for my needs. I usually only need to use merge when I have objects that can have references to objects of the same type. Most recently, the cause of the exception was in the code validating that the reference wasn’t recursive. I was loading the same object into my session as part of the validation, causing the error.
Where have you encountered this problem? Did merge work for you or did you need another solution? Do you prefer to always use merge, or prefer to use it only as needed for specific cases
Upvotes: 12
Reputation: 39456
╔══════════════╦═══════════════════════════════╦════════════════════════════════╗
║ METHOD ║ TRANSIENT ║ DETACHED ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ sets id if doesn't ║ sets new id even if object ║
║ save() ║ exist, persists to db, ║ already has it, persists ║
║ ║ returns attached object ║ to DB, returns attached object ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ sets id on object ║ throws ║
║ persist() ║ persists object to DB ║ PersistenceException ║
║ ║ ║ ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ ║ ║
║ update() ║ Exception ║ persists and reattaches ║
║ ║ ║ ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ copy the state of object in ║ copy the state of obj in ║
║ merge() ║ DB, doesn't attach it, ║ DB, doesn't attach it, ║
║ ║ returns attached object ║ returns attached object ║
╠══════════════╬═══════════════════════════════╬════════════════════════════════╣
║ ║ ║ ║
║saveOrUpdate()║ as save() ║ as update() ║
║ ║ ║ ║
╚══════════════╩═══════════════════════════════╩════════════════════════════════╝
Upvotes: 118
Reputation: 91
None of the following answers are right. All these methods just seem to be alike, but in practice do absolutely different things. It is hard to give short comments. Better to give a link to full documentation about these methods: http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/objectstate.html
Upvotes: 1
Reputation: 59
Actually the difference between hibernate save()
and persist()
methods is depends on generator class we are using.
If our generator class is assigned, then there is no difference between save()
and persist(
) methods. Because generator ‘assigned’ means, as a programmer we need to give the primary key value to save in the database right [ Hope you know this generators concept ]
In case of other than assigned generator class, suppose if our generator class name is Increment means hibernate it self will assign the primary key id value into the database right [ other than assigned generator, hibernate only used to take care the primary key id value remember ], so in this case if we call save()
or persist()
method then it will insert the record into the database normally
But hear thing is, save()
method can return that primary key id value which is generated by hibernate and we can see it by
long s = session.save(k);
In this same case, persist()
will never give any value back to the client.
Upvotes: 5
Reputation: 1751
Be aware that if you call an update on an detached object, there will always be an update done in the database whether you changed the object or not. If it is not what you want you should use Session.lock() with LockMode.None.
You should call update only if the object was changed outside the scope of your current session (when in detached mode).
Upvotes: 2