in hibernate if I persist new entity it should become managed , moreover it should return the managed entity.
if the entity is detached , the merge will return managed entity but the one that I passed will still detached.
I've tried that in spring boot with hibernate and everything is working except the following case :
User transientUser=new User();
transientUser.setId(9L);
User managedTransientUser=userRepository.save(transientUser);
if I set the Id manually ( even with removing the auto generation) the entity that I pass ( transientUser) will still be unmanaged . If I use generation Identity then the returned and passed entity are the same ( the id is null and the DB will auto increment ) is that expected ?
That's simply because you're not calling persist(). You're calling userRepository.save().
This method tests if the entity is new (by checking if it already has an ID). If it is, it calls persist(). Otherwise it calls merge(). Since your entity already has an ID, it calls merge().
Related
We are using Toplink implementation of JPA + Spring + EJB. In one of our EJBs we have something like this:
public void updateUser(long userId, String newName){
User u = em.get(User.class, userId);
u.setName(newName);
// no persist is invoked here
}
So, basically this updateUser() method is supposed to update the name of a user with the given userId.
But the author of this method forgot to invoke em.persist(u).
And the strangest thing is that it works fine. How can it be? I was 100% sure that
without invoking em.persist() or em.merge() there is no way that changes could have been saved into database. Could they? Is there any scenario when this could happen?
You're working with a managed entity. If the entity does not become detached because its entity manager is closed, all changes done to the entity are reflected to the database when the session is flushed/closed and the transaction commited.
From the Java EE tutorial:
The state of persistent entities is
synchronized to the database when the
transaction with which the entity is
associated commits.
Edit for clarity and explanation: So there are three distinct modes that an entity could be in during its lifecycle:
Unsaved: The entity has been instantiated, but persist() has not been called yet.
Managed: The entity has been persisted using persist(), or loaded from the database, and is associated with an entity manager session. All changes to the entity are reflected to the database when the entity manager session is flushed.
Detached: The entity's entity manager session was closed. Changes to the entity will not be reflected to the database automatically, but can be merged explicitly using the merge() command.
Lets say a client updates an entity ( Ex: student entity ).
So we get the student Id and other modified fields (not all fields) from the client.
I read that we should pass the particular entity object to DAO in order to update.
But then , how will I get to form that entity object.Because I don't have all fields data to create a proper entity object.
Should I make two DB calls ?
The first call is to construct a proper entity object and then make the update by passing this updated entity object to the DAO.
The only way to avoid two DB calls is to use an update statement to update only th fields you have. E.g.
UPDATE Student SET someField1 = :field1, someField2 = :field2 WHERE ID = :id
Remember that update queries bypass optimistic locking checks.
If you use optimistic locking you should append the version to the where clause and also increment it.
UPDATE Student SET someField1 = :field1, version = version + 1 WHERE id = :id AND version = :version
After an executeUpdate you should check the affected rows:
1 : everything is ok
0 : the entity could either not be found by it's id. Maybe it was deleted in the meanwhile or the version did not match. In both cases you should raise an OptimisticLockException.
>1 : you should raise an exception to rollback the transaction.
I had an app with the following code working just fine until I upgraded hibernate (5.3.2 to 5.4.10 )
List<UserRole> roles = entity.getRoles();
for(UserRole r : roles) {
Em.get().remove(r);
}
roles.clear();
for(RoleEnum r : selectedRoles) {
UserRole role = new UserRole(entity, r);
Em.get().persist(role);
}
Em.get().merge(entity);
Em.get().flush();
So, then I started getting an exception
Caused by: org.hibernate.TransientPropertyValueException: object
references an unsaved transient instance - save the transient instance
before flushing : WEBPIECESxPACKAGE.base.libs.UserRole.user ->
WEBPIECESxPACKAGE.base.libs.UserDbo
This would happen when I 'add' a new user entity. If I edit an old user(it uses the same exact code), then it would be fine.
I changed to Em.get().persist(entity) instead and that works for adding a new entity to DB and for editing an old one.
BUT the documentation still says what old JPA/hibernate used to do for persist which is
#throws EntityExistsException if the entity already exists.
Is everyone using persist now as the add or edit function? (ie. having one function that saves or edits as I don't really care which is very very nice AND hibernate can tell from the DB id existing or not whether it is an add or an edit so there is no reason to not have a single call for both).
I am NOW using em.persist() which is working for UPDATE or SAVE...weird
It can be seen on line 110 here
https://github.com/deanhiller/webpieces/blob/master/webserver/webpiecesServerBuilder/templateProject/WEBPIECESxAPPNAME/src/main/java/webpiecesxxxxxpackage/web/crud/CrudUserController.java
I am using Hibernate 5.4.10
thanks,
Dean
Possible Duplicate of Update Vs Merge
Whats happening here is:
Edit Mode :
List<UserRole> roles = entity.getRoles(); //Gets Existing Roles from DB
for(UserRole r : roles) {
Em.get().remove(r); //Removes Roles to existing user
}
roles.clear(); // Clean up local memory
for(RoleEnum r : selectedRoles) { // User Input Roles
UserRole role = new UserRole(entity, r); // New Entity with existing user
Em.get().persist(role); // Role Entity Referenced to existing user object, saved
}
Em.get().merge(entity); // ?? No Need in edit unless roles are stored in user table
Em.get().flush();
New User Mode :
List<UserRole> roles = entity.getRoles(); // New Detached User Entity Roles
for(UserRole r : roles) { // Probably Empty Roles Array
Em.get().remove(r); // Removed roles
}
roles.clear(); // Clean up Memory
for(RoleEnum r : selectedRoles) { // Copy from App Roles
UserRole role = new UserRole(entity, r); //Create new role
Em.get().persist(role); //Save Role to DB
}
Em.get().merge(entity); // Trying to merge non existing Entity <-- This is where error appears
Em.get().flush();
The persist method works because it has decides when to use insert or update command. Since new user entity has no ID set to it, it has no idea what to do with it, while it may have worked in past, actual behavior of mergig is very well explain in this thread merging a detached or new entity with an existing entity in hibernate/jpa best practice question
See for yourself :
If your entity is a detached entity the only thing u really need to do
is to invoke entityManager.merge(user). You dont need to exec any
finder method. If your entity is not detached but rather new (it does
not have id specified) you should find appropriate entity in the
database prior performing any modification operations on that entity
and merge it afterwards.
Another detailed reference is given here : persist() and merge() in JPA and Hibernate
Here is the reference from docs :
Serializable
save(Object object) throws HibernateException
Persist the given transient instance, first assigning a generated identifier. (Or using the current value of the identifier property if the assigned generator is used.) This operation cascades to associated instances if the association is mapped with cascade="save-update".
Parameters:
object - a transient instance of a persistent class
Returns:
the generated identifier
Throws:
HibernateException
persist
void persist(String entityName,
Object object)
throws HibernateException
Make a transient instance persistent. This operation cascades to associated instances if the association is mapped with cascade="persist".
The semantics of this method are defined by JSR-220.
Parameters:
object - a transient instance to be made persistent
Throws:
HibernateException
merge
Object merge(String entityName,
Object object)
throws HibernateException
Copy the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance. If the given instance is unsaved, save a copy of and return it as a newly persistent instance. The given instance does not become associated with the session. This operation cascades to associated instances if the association is mapped with cascade="merge".
The semantics of this method are defined by JSR-220.
Parameters:
object - a detached instance with state to be copied
Returns:
an updated persistent instance
Throws:
HibernateException
save() and persist() result in an SQL INSERT, delete() in an SQL
DELETE and update() or merge() in an SQL UPDATE. Changes to persistent
instances are detected at flush time and also result in an SQL UPDATE.
saveOrUpdate() and replicate() result in either an INSERT or an
UPDATE.
Conclusion: Functions are behaving as they are intended.
I'm using Spring-Data JPA and Spring-MVC with a RESTful interface. I'm trying to implement a basic CRUD controller. I'm having some difficulty figuring out the best way to implement the "Update".
My basic controller method is straight forward:
#RequestMapping( method=RequestMethod.POST, value="updateUser", produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public User update( #RequestBody final User user){
userRepository.save(user);
return user;
}
However, this only seems to provide a "create" - not an "update". Everytime the method is called, a new user is created in the DB, even if I specify the User PK in the JSON object.
From a quick look at the SimpleJpaRepository class, it creates a new object whenever the "version" field is missing. However, if I force the "version" field to have a value, I get an exception (not surprisingly):
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.ia.domain.User#5]
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:898)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:902)
at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:889)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
I realize that one option is to first query the DB for the existing User object (based on the submitted PK), then copy all the fields over and then save that object, but that does not seem like the right way. I presume there must be a way for me to "merge" my User object and just update it, but am not entirely sure how.
Is there a simple way of doing this?
Tracking the error to the hibernate code I found there is problem related to the version
else if ( isVersionChanged( entity, source, persister, target ) ) {
299 if ( source.getFactory().getStatistics().isStatisticsEnabled() ) {
300 source.getFactory().getStatisticsImplementor()
301 .optimisticFailure( entityName );
302 }
303 throw new StaleObjectStateException( entityName, id );
304 }
/VersionChanges is
342 boolean changed = ! persister.getVersionType().isSame(
343 persister.getVersion( target ),
344 persister.getVersion( entity )
345 );
The error is coming from different version of the entity,
Consider using to save the standard method of JPARepository.
#Transactional
public <S extends T> S save(S entity)
Also update your question with the code for userRepository, as I think saveUser is your own implementation
After more research and debugging and trial & error, it turns out the solution is very simple. The value of the version field of the entity being persisted must match what is currently in the DB. If the values don't match Hibernate presumes that something else updated the DB and consequently throws the org.hibernate.StaleObjectStateException.
Ensuring that the JSON being submitted has a matching PK and version value as the row in the DB, then the repo.save(user) will update the existing row. If the version field is null, the entity is persisted as a new row.
I don't think you should "force the version field to have value". They're there for the purpose of optimistic locking. Other transaction could be modifying your record and the version you're working at become stale.
As with your entity object not updating, Spring might failed to detect it's a detached entity because the primary key field mapping was wrong / not setup. It could also be due to the entity never been persisted in the first place (hence it's in new state)
I believe you want to use userRepository.merge(user).
I am using #Version annotation to provide version control in hibernate. My question is regarding the proper mapping of data from DTO to Entity.
What I feel is the right way is as follows but I want to know if there is a better way or this is how everybody does it.
call comes to my service
i load the entity to be updated (assume AddressEntity with version = 1)
i map the AddressDTO values to AE, including sub-collections if any
after all mapped, i detach the entity AE (only to be detached after Lazy sub collections mapped too)
now i map the version from DTO to AE (as hibernate does not allow to update version in managed entity)
now i call merge to update this detached AE entity
1) Is this the right way semantics and logic wise ?
2) (bit out of context) is there an overhead for hibernate to merge an object already in context and managed ie can i use merge for all updates safely irrespective or managed/unmanaged or Only merge+flush for unmanaged and flush for managed after updating some properties ?
Let me try to answer your question stepwise:
Suppose you have loaded an AddressEntity (having id=123 and version=1). Set the property values from AddressEntity to AddreeDto including the id and version values. Send the AddressDto to UI.
Changes made to AddresDto. Call has come to your service. Create an instance of AddressEntity and set the values from AddressDto including the id and version values. This new AddressEntity has now turned into a detached instance, as it has a persistent identity, but its state is not guaranteed to be synchronized with database state.
Hibernate lets you reuse this Addressentity instance in a new transaction by reassociating it with a new persistence manager.This detached instance can be reassociated with a new Session by calling update(). You don't need to load the entity again.The update() method forces an update to the persistent state of the object in the database.
Set the addressEntity properties:
addressEntity.setId(dto.getId());
addressEntity.setVersion(dto.getVersion());
Attach addressEntity to a new session:
Transaction tx = sessionTwo.beginTransaction();
sessionTwo.update(addressEntity);
tx.commit();
sessionTwo.close();
The session.update will execute an SQL similar to this:
update ADDRESS_ENTITY set ... , VERSION=2
where ID=123 and VERSION=1
If another application transaction would have updated the same ADDRESS_ENTITY since it was loaded, the VERSION column would not contain the value 1, and the row would not be updated, and you will receive a stale object state exception. You can catch the exception and inform the User about the stale data.
after all mapped, i detach the entity AE (only to be detached after Lazy sub collections mapped too)
Assuming you are performing this in a single transaction. Any persistent object that you have retrieved from DB is associated with the current session and transaction context. If it is modified in the same transaction, its state will be automatically synchronized with the DB. This mechanism is called automatic dirty checking. It means Hibernate will track and save the changes made to an object inside a session.
Transaction tx = session.beginTransaction();
int addressEntityID = 1234;
AddressEntity addressEntity = (AddressEntity) session.get(AddressEntity.class, new Long(addressEntityID));
// set the values from AddressDTO to AddressEntity
tx.commit();
session.close();
The object is retrieved from DB, it is modified and the modifications are propagated to DB on transaction commit.You don't need to detach and reattach an entity to perform an update.
now i map the version from DTO to AE (as hibernate does not allow to update version in managed entity)
The managed versioning is used to implement optimistic locking and the versioning of the entities is managed by Hibernate. The version number is just a counter value, it does not have any useful information that you should keep in your DTO.You don’t need to set the value of the version yourself. Hibernate will initialize the value when you first save an AddressEntity, and increment or reset it whenever the object is modified.
If another application transaction(T2) updates the persistent instance the same item since it was read by the current application transaction(T1), the T2 transaction will change the version value for this entity. Now when T1 tries to make an update, Hibernate will throw a stale object state exception, as the version of the entity has been changed. You can catch the exception and inform the User about the stale data. In particular, versioning prevents the lost update problem. You don't need to map the version from DTO to AE or from AE to DTO, as it does not have any meaningful information which can be used in contexts other than to implement optimistic locking.