I'm confused about how I should refresh the state of entity that is already in the database. Being more specific, suppose I have "entity" persisted with a code like this:
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(entity);
em.getTransaction().commit();
entityManager.close();
Since I closed the EntityManager my entity instance is detached. Now suppose that I have other objects using this instance of entity. If I want to fetch the new state of this entity from the database, I pretty much can't use em.refresh() because the entity is detached. The em.merge() method returns a managed instance, and since is not the same instance of my object this can be a problem. I can foresee two solutions:
create a new method in my entity object that updates its state using a given entity instance.
not close the entity manager (implications !??)
So, what I should do in this case? How can I refresh the state of my entity object without losing all the references from other objects to it? Ideas?
If entity A references entity B that is detached, merging B returns B', and refresh B'. If you merge A, A will change its reference of B to B'.
A ---> B --(merge)--->B'
(refresh)
/
merge A -----------/
To avoid the changes being made to an entity by refreshing & getting detached after persisting, can implement the Cloneable interface & then processing the cloned entity accordingly.
//---
XEntity cloneX = (XEntity) entity.clone();
cloneX = entityManager.merge(cloneX);/* Persisting & getting synchronized copy */
// entityManager.refresh(cloneX); /* not need */
cloneX.copyTo(entity); // Add required changes back to entity if any
//---
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.
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 am new with this language. I have some rows in employee table and the bean class is Employee. I have fetched one record
Employee employee=this.employeeDaoImpl.getEmployeeObject(employeeId);
This is the CONTROLLER
#Transactional
#RequestMapping(value="/revise_payroll")
public String revise_payroll(HttpServletRequest req,HttpServletResponse resp, Model model,RedirectAttributes redirect){
System.out.println("in revise payroll");
String employeeId=req.getParameter("employeeId");
System.out.println("E_ID for revise:"+employeeId);
List<IncrementDecrementPayrollTemp> tempPayrollList=this.employeeDaoImpl.getTemporaryPayroll(employeeId);
//get employee object from session
List<Employee> empList=this.employeeDaoImpl.getCurrentCTC(employeeId);
System.out.println("empList has: "+empList.toString());
Employee employee=this.employeeDaoImpl.getCurrentCTCasObject(employeeId);
System.out.println(("in controller employee hashcode: "+employee.toString()));
int count=0;
// this will run for only one time
for(IncrementDecrementPayrollTemp tempPayroll:tempPayrollList){
employee.setCtc(tempPayroll.getCtct());
employee.setBasicMonthly(tempPayroll.getBasicMonthlyt());
employee.setBasicAnnual(tempPayroll.getBasicAnnualt());
employee.setDaMonthly(tempPayroll.getDaMonthlyt());
employee.setDaAnnual(tempPayroll.getDaAnnualt());
employee.setHouserentMonthly(tempPayroll.getHouserentMonthlyt());
employee.setHouserentAnnual(tempPayroll.getHouserentAnnualt());
employee.setConveyanceMonthly(tempPayroll.getConveyanceMonthlyt());
employee.setConveyanceAnnual(tempPayroll.getConveyanceAnnualt());
employee.setMedicalMonthly(tempPayroll.getMedicalMonthlyt());
employee.setMedicalAnnual(tempPayroll.getMedicalAnnualt());
employee.setSpecialMonthly(tempPayroll.getSpecialMonthlyt());
employee.setSpecialAnnual(tempPayroll.getSpecialAnnualt());
employee.setPfMonthly(tempPayroll.getPfMonthlyt());
employee.setPfAnnual(tempPayroll.getPfAnnualt());
employee.setEsiMonthly(tempPayroll.getEsiMonthlyt());
employee.setEsiAnnual(tempPayroll.getEsiAnnualt());
employee.setMonthlySalary(tempPayroll.getMonthlySalaryt());
}
return new ModelAndView ("IncrementDecrementStatus");
}
Now, when I am just calling the setter methods on employee object, its updating the sql records, in the controller itself. I am not yet in DAO layer using session.save or any update function.
This is DAO Layer
Session session=this.sessionFactory.getCurrentSession();
String p=employeeId.trim();
String hql="From Employee e where e.employeeId=?";
Query query=session.createQuery(hql);
query.setString(0, p);
List<Employee> employeeList=(List<Employee>)query.list();
System.out.println("dao list has "+employeeList.toString());
// to update the existing records
for(Employee emp:employeeList){
int id=emp.getId();
System.out.println("id got: "+id);
Employee empl=(Employee) session.get(Employee.class, id);
String version=empl.getVersion();
System.out.println("version is: "+version);
int intVersion=Integer.valueOf(version);
intVersion=intVersion+1;
version=String.valueOf(intVersion);
empl.setVersion(version);
System.out.println("version and ctc in empl is: "+empl.getVersion()+" , "+empl.getCtc());
System.out.println("hash code in loop: "+empl.toString());
session.update(empl);
}
// this is to save new record
Integer i=(Integer)session.save(sessionEmployee);
System.out.println("save returned: "+i.toString());
}
Things I want to achieve is, I want to update the existing records already in sql table and then save the employee object with some new set of values as a new record. Please suggest me where I am wrong. Thank you!
Let me tell you the lifecycle states of an entity which can make you more clear about this behaviour.
An entity can exist in three states - Transient, Persistent and Detached.
Transient - When you create an object but do not associate it with Hibernate session, then it is in Transient state. Any modifications to such object using setter methods doesn't reflect the change in the database.
Persistent - Here the object is attached to the Hibernate session. So now the Hibernate session manages this object. Any changes made to this object gets reflected in the database. Because Hibernate designed it in such way that, if any modifications is made to a Persistent object, it automatically gets updated in the database, when the session is flushed. (This is Hibernate's capability).
Detached - This state is similar to Transient. The only difference is that an object in detached state was previously in the session(i.e. in persistent state ). But now this is out of the session, because of either closing of the session or calling the evict(Object) method of session.
So coming to your case, once you have loaded the object from database, the object is associated with the session, and thus is in persistent state. As this object is in Persistent state, and you made changes to a Persistent object, the changes are reflected back to database.
Coming to your requirement, (Dividing the problem into parts)
You want to get an existing record from the table - Use Employee empl=(Employee) session.get(Employee.class, id);
Now you want to make changes to this object but not to the database. So use session.evict(empl); to bring the object to detached state. Then after this, you can make modifications to the detached empl object.
Now you want to save this set of new values as a new record. So make sure you change the "id" property of the empl object, as you can't violate unique constraint of the id value. You can't have two records with the same id value in the table.
Don't forget to commit the transaction.
That's normal behaviour. If you load an entity and modify it while it's still managed by the EntityManager, it will propagate all changes back to the database.
You can use evict(employee) to make the bean unmanaged.
Chang performed on any attached entity , hibernate automatically detect and commit to DB. either you can detached loaded entity by evict(entity) or create transient entity by clone of attached entity to use it further in you code.
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.
Does remove(Object entity) method of EntityManager work only on those objects got from find() method?
I have following code snippet:
public void deletePerson() {
EntityManager em = getEntityManager();
Person p = new Person("x", "y", 200);
em.remove(p);
}
But it is not removing the particular entry from database.
When I tried something like below:
public void deletePerson() {
EntityManager em = getEntityManager();
Person p = em.find(Person.class, 200);
em.remove(p);
}
It's working fine.
Quoting from ObjectDB's manual on deleting JPA entity objects:
In order to delete an object from the database it has to first be
retrieved (no matter which way) and then in an active transaction, it
can be deleted using the remove method.
An IllegalArgumentException is thrown by remove if the argument is not
a an instance of an entity class or if it is a detached entity.
When creating object with new operator, it becomes a detached entity, you need to persist it if you want to remove it.
When retrieving entity, you are retrieving persistent entity.
Something to that direction. EntityManager.remove works only for managed entities. How you obtained these managed entities does not matter, it can be for example:
via JPQL query
via Criteria API query
find method in EntityManager
by following relationship from some other entity.
created new entity and persisted it
But simply creating new object and trying to remove it does not work, because this new object is not managed entity. Also entity should not be yet detached.
Life of entity is quite much as follows, all in same transaction (entities outside their transaction are not managed):
Entity ent = new Entity(1); //entity is in new state, EntityManager never know
//anything about it
em.persist(ent); //entity is managed as long as not disconnected
//from EntityManager
em.clear(); // all previously managed entities, including ent, are now detached
Entity same = em.find(1); //managed same
em.remove(same); // entity is removed
Yes in case of merge or remove operation you have to use a find() operation and then use the remove method on the retrieved entity.
With JPA, you can remove an entity without retrieving it by simply executing a delete statement:
javax.persistence.Query q= entityManager.createQuery("delete from A where id = :id");
q.setParameter("id", "value of id to delete");
int deletedRows = q.executeUpdate();