my app consists of spring #transaction with hibernate. I am trying to use hibernate interceptor. i registered the interceptor as
getHibernateTemplate().getSessionFactory().withOptions().interceptor(interceptor).openSession();
And the actual interceptor extends EmptyInterceptor and i am overriding onSave() method.
the problem is that the interceptor onSave() method is called but after that , the actual entity does not get inserted in the database.
public boolean onSave(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types) {
System.out.println("inside interceptor - on save");
{
// my changes here . setting a field of the entity.
return true;
}
return false;
}
According to Interceptor.onSave():
Called before an object is saved. The interceptor may modify the state, which will be used for the SQL INSERT and propagated to the persistent object.
So the changes are commited to the persistent object and the INSERT statement that Hibernate will issue to the database.
You might want to make sure you are searching for the record once the #Transactional method has finished, if you're querying the database with a different session than the one that opened the transaction. That is when Spring will commit the transaction to the database.
You could also try using Interceptor.afterTransactionCompletion() to check if the record is effectvely inserted.
Called after a transaction is committed or rolled back.
Related
I am trying to capture the entity data in the database before the save is executed, for the purpose of creating a shadow copy.
I have implemented the following EntityListener in my Spring application:
public class CmsListener {
public CmsListener() {
}
#PreUpdate
private void createShadow(CmsModel entity) {
EntityManager em = BeanUtility.getBean(EntityManager.class);
CmsModel p = em.find(entity.getClass(), entity.getId());
System.out.println(entity);
}
}
The entity does indeed contain the entity object that is to be saved, and then I inject the EntityManager using another tool, which works fine - but for some reason, the entity has already been saved to the database. The output of CmsModel p = em.find(...) results in identical data which is in entity.
Why is JPA/hibernate persisting the changes before #PreUpdate is called? How can I prevent that?
I would assume this is because em.find doesn't actually query the database but fetches the object from cache, so it actually fetches the same object entity refers to (with changes already applied).
You could check your database log for the query that fetches the data for entity.id to verify this is indeed the case or you could add a breakpoint in createShadow() and have a look at the database entry for entity at the time the function is called to see for yourself if the changes are already applied to the database at that time.
To actually solve your problem and get your shadow copy you could fetch the object directly from database via native query.
Here is an untested example of what this could look like:
public CmsModel fetchCmsModelDirectly(){
Query q = em.createNativeQuery("SELECT cm.id,cm.value_a,cm.value_b FROM CmsModel cm", CmsModel.class);
try{
return q.getSingleResult();
}catch(NoResultException e){
return null;
}
}
Do you check if the entity is really updated to database? My suspect is that the change is only updated to the persistence context (cache). And when the entity is query back at the listener, the one from the cache is returned. So they are identical.
This is the default behavior of most of the ORM (JPA in this case) to speed up the data lookup. The ORM framework will take care of the synchronizing between the persistence context and the database. Usually when the transaction is committed.
I am trying to understand the behavior of transaction propagation using SpringJTA - JPA - Hibernate.
Essentially I am trying to update an entity. To do so I have written a test method where I fetch an object using entity manager (em) find method ( so now this object is manged object). Update the attributes of the fetched object. And then optionally make a call to service layer(service layer propagation=required) which is calling em.merge
Now I have three variations here :
Test method has no transactional annotation. Update the attributes
of the fetched object and make no call to service layer.
1.1. Result level 1 cache doesn't gets updated and no update to DB.
Test method has no transactional annotation. Update the attributes of the fetched object. Call the service layer.
2.1. Result level 1 cache and DB gets updated.
Test method has Transnational annotation which could be any of the following. Please see the table below for Propagation value at the test method and the outcome of a service call.
(service layer propagation=required)
So to read the above table, the row 1 says if the Test method has transaction propagation= REQUIRED and if a service layer call is made then the result is update to Level 1 cache but not to the DB
Below is my test case
#Test
public void testUpdateCategory() {
//Get the object via entity manager
Category rootAChild1 = categoryService.find(TestCaseConstants.CategoryConstant.rootAChild1PK);
assertNotNull(rootAChild1);
rootAChild1.setName(TestCaseConstants.CategoryConstant.rootAChild1 + "_updated");
// OPTIONALLY call update
categoryService.update(rootAChild1);
//Get the object via entity manager. I believe this time object is fetched from L1 cache. As DB doesn't get updated but test case passes
Category rootAChild1Updated = categoryService.find(TestCaseConstants.CategoryConstant.rootAChild1PK);
assertNotNull(rootAChild1Updated);
assertEquals(TestCaseConstants.CategoryConstant.rootAChild1 + "_updated", rootAChild1Updated.getName());
List<Category> categories = rootAChild1Updated.getCategories();
assertNotNull(categories);
assertEquals(TestCaseConstants.CategoryConstant.rootAChild1_Child1,categories.get(0).getName());
}
Service Layer
#Service
public class CategoryServiceImpl implements CategoryService {
#Transactional
#Override
public void update(Category category) {
categoryDao.update(category);
}
}
DAO
#Repository
public class CategoryDaoImpl {
#Override
public void update(Category category) {
em.merge(category);
}
}
Question
Can someone please explain why does REQUIRED, REQUIRES_NEW, and NESTED doesn't lead to insertion in the DB?
And why absence of transaction annotation on Test case lead to insertion in the DB as presented in my three variations?
Thanks
The effect you're seeing for REQUIRED, NESTED, and REQUIRES_NEW is due to the fact that you're checking for updates too early
(I'm assuming here that you check for db changes at the same moment when the test method reaches the assertions, or that you roll the test method transaction back somehow after executing the test)
Simply enough, your assertions are still within the context created by the #Transactional annotation in the test method. Consequently, the implicit flush to the db has not been invoked yet.
In the other three cases, the #Transactional annotation on the test method does not start a transaction for the service method to join. As a result, the transaction only spans the execution of the service method, and the flush occurs before your assertions are tested.
In one of the Rest controllers of my spring app, an instance of an entity gets loaded from the db, a field of it is changed through calling setXX method on that entity and finally it's saved back to the db .
I wonder if it is possible to update this instance in the database automatically after each call to its setXX methods. I know that performance-wise it is not ideal, but it would work for me in some cases.
Edit 1
This is a test code where I'm saving/loading the entity. I call the following helper method in a util class to save user details to the db:
public CustomUserDetails createUser(String username, String name, String description, String imageId) {
User user = new User();
user.setName(name);
user.setDescription(description);
user.setImageId(imageId);
CustomUserDetails userDetails = new CustomUserDetails(username,
DUMMY_PASSWORD, true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
userDetails.setUser(user);
userDetailsRepository.save(userDetails);
return userDetails;
}
And in the test, I get the User instance by calling getUser on the returned value from this method. I setXX some values, but it's not persisted.
Following the code excerpt addition
User user = new User();
This is not a Managed entity.. You'll need to persist it and/or retrieve it from DB for your follow-up setter calls to be persisted (assuming Transaction is still on).
As long as the Entity is in the Managed/Persistent state then (as per the documentation)
any changes will be automatically detected and persisted when the
persistence context is flushed. There is no need to call a particular
method to make your modifications persistent.
Also as it's commented, within a Spring #Transactional method, fetching an Entity (therefore being in the Managed/Persistent state) followed directly by a setter property call will result in persisting the property.
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.
The method loads a user entity bean from the database and returns it, but before doing that clears the password (setting it with null). The issue is that in the database itself the password is set with null, even though I don't have a merge or any other method that updates the entity. Any ideas?
public UserEntity loadFromDB (int userid) throws NotFoundException {
UserEntity user = em.find(UserEntity.class, userid);
if (user == null)
throw new NotFoundException();
user.setPassword(null);
return user;
}
It seems like the method loadFromDB is called within a transaction and so your Entity is attached and getting saved when transaction is closed.
Your DAO or service layer has transaction marked at class level and so the method is within a transaction.
If your method is part of transaction then it is basically adding below at start and end of transaction and it will commit updates to all the entities within transaction.
entityManager.getTransaction().begin();
// updates to entity
entityManager.getTransaction().commit(); //Saves all updates to DB
You can avoid it by doing that update outside your transaction in your view layer or by marking that method as not being part of transaction.
Mark your method as not part of transcation by adding
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)