Java Hibernate: Persist automatically after setting a field - java

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.

Related

JPA flushing to Database before #PreUpdate is called

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.

Why does #Transactional isolation level have no effect when updating entities with Spring Data JPA?

For this experimental project based on the spring-boot-starter-data-jpa dependency and H2 in-memory database, I defined a User entity with two fields (id and firstName) and declared a UsersRepository by extending the CrudRepository interface.
Now, consider a simple controller which provides two endpoints: /print-user reads the same user twice with some interval printing out its first name, and /update-user is used to change the user's first name in between those two reads. Notice that I deliberately set Isolation.READ_COMMITTED level and expected that during the course of the first transaction, a user which is retrieved twice by the same id will have different names. But instead, the first transaction prints out the same value twice. To make it more clear, this is the complete sequence of actions:
Initially, jeremy's first name is set to Jeremy.
Then I call /print-user which prints out Jeremy and goes to sleep.
Next, I call /update-user from another session and it changes jeremy's first name to Bob.
Finally, when the first transaction gets awakened after sleep and re-reads the jeremy user, it prints out Jeremy again as his first name even though the first name has already been changed to Bob (and if we open the database console, it's now indeed stored as Bob, not Jeremy).
It seems like setting isolation level has no effect here and I'm curious why this is so.
#RestController
#RequestMapping
public class UsersController {
private final UsersRepository usersRepository;
#Autowired
public UsersController(UsersRepository usersRepository) {
this.usersRepository = usersRepository;
}
#GetMapping("/print-user")
#ResponseStatus(HttpStatus.OK)
#Transactional (isolation = Isolation.READ_COMMITTED)
public void printName() throws InterruptedException {
User user1 = usersRepository.findById("jeremy");
System.out.println(user1.getFirstName());
// allow changing user's name from another
// session by calling /update-user endpoint
Thread.sleep(5000);
User user2 = usersRepository.findById("jeremy");
System.out.println(user2.getFirstName());
}
#GetMapping("/update-user")
#ResponseStatus(HttpStatus.OK)
#Transactional(isolation = Isolation.READ_COMMITTED)
public User changeName() {
User user = usersRepository.findById("jeremy");
user.setFirstName("Bob");
return user;
}
}
There are two issues with your code.
You are performing usersRepository.findById("jeremy"); twice in the same transaction, chances are your second read is retrieving the record from the Cache. You need to refresh the cache when you read the record for the second time. I have updated code which uses entityManager, please check how it can be done using the JpaRepository
User user1 = usersRepository.findById("jeremy");
Thread.sleep(5000);
entityManager.refresh(user1);
User user2 = usersRepository.findById("jeremy");
Here are the logs from my test case, please check SQL queries:
The first read operation is completed. Thread is waiting for the timeout.
Hibernate: select person0_.id as id1_0_0_, person0_.city as city2_0_0_, person0_.name as name3_0_0_ from person person0_ where person0_.id=?
Triggered update to Bob, it selects and then updates the record.
Hibernate: select person0_.id as id1_0_0_, person0_.city as city2_0_0_, person0_.name as name3_0_0_ from person person0_ where person0_.id=?
Hibernate: update person set city=?, name=? where id=?
Now thread wakes up from Sleep and triggers the second read. I could not see any DB query triggered i.e the second read is coming from the cache.
The second possible issue is with /update-user endpoint handler logic. You are changing the name of the user but not persisting it back, merely calling the setter method won't update the database. Hence when other endpoint's Thread wakes up it prints Jeremy.
Thus you need to call userRepository.saveAndFlush(user) after changing the name.
#GetMapping("/update-user")
#ResponseStatus(HttpStatus.OK)
#Transactional(isolation = Isolation.READ_COMMITTED)
public User changeName() {
User user = usersRepository.findById("jeremy");
user.setFirstName("Bob");
userRepository.saveAndFlush(user); // call saveAndFlush
return user;
}
Also, you need to check whether the database supports the required isolation level. You can refer H2 Transaction Isolation Levels
Your method to update #GetMapping("/update-user") is set with an isolation level #Transactional(isolation = Isolation.READ_COMMITTED) so commit() step is never reached in this method.
You must change isolation level or read your value in your transaction to commit the changes :)
user.setFirstName("Bob"); does not ensure your data will be committed
Thread Summary will look like this :
A: Read => "Jeremy"
B: Write "Bob" (not committed)
A: Read => "Jeremy"
Commit B : "Bob"
// Now returning "Bob"

Save and Update operations in single Transaction of JPA not working as expected

consider entity as user, it having some fields. here i am using jpa callback functions to update user information of last update information. in test class I want to write junit method to evaluate whether these call back methods are working or not/ not only for functionality testing and code coverage purpose also. but if I follow below approach i am getting same time everytime, can anyone help on this.
#Entity
public class User {
// user setter and getter methods
#preUpdate
public void preUpdateFunction() {
this.lastUpdateDate = new Date();
}
#prePersist
public void prePersistFunction() {
// setting some user properties
preUpdateFunction();
}
}
// please ignore this configuration and annotations setup, I tested my class spring configuration working perfectly there is no issue with spring configuration.
#SpringConfiguration
#JpaTransactional
public class TestClass {
#Autowired
UserDao userDao; // userDao implements JPA Repository
// I am worrying about this functionality only
#Test
public void saveUpdateTest() {
User user = userDao.save(new User(constructor arguments));
user = userDao.findOne(user.getId());
user.setName("Hello"); // here updating user object with existing property
User updatedUser = userDao.save(user);
assertEquals(user.getLastUpdateDate().getTime(), updatedUser.getLastUpdateDate().getTime());
// assertion is failing, everytime i am getting same Time for two values. even I added //Thread.sleep(1000) between save and update operations, still i am getting same values.
}
}
Short answer
You need to call saveAndFlush
User updatedUser = userDao.saveAndFlush(user);
Long answer
From JPA spec (JSR 338 JPA 2.1):
The PrePersist and PreRemove callback methods are invoked for a given entity before the
respective EntityManager persist and remove operations for that entity are executed.
The PreUpdate and PostUpdate callbacks occur before and after the database update operations to entity data respectively. These database operations may occur at the time the entity state is updated or
they may occur at the time state is flushed to the database (which may be at the end of the transaction).
#PrePersist is invoked when entityManager persist operation is executed. Tricky part is the execution is usually delayed until next flush operation or transaction commit (hibernate default config). Generally, it depends on flush configuration and on jpa implementation.
Same also applies to #PreUpdate. In addition, JPA spec says it more specifically, it might occur when entity state is updated or when flushed to DB.
Related links
https://download.oracle.com/otn-pub/jcp/persistence-2_1-fr-eval-spec/JavaPersistence.pdf
https://thorben-janssen.com/spring-data-jpa-save-saveandflush-and-saveall

Record not inserted after interceptor onSave()

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.

Hibernate updating the record upon calling the setter methods of the bean class

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.

Categories

Resources