Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA merge() best practice

I implemented a service for entity object and it uses pure jpa, I used spring, so in spring xml config configured hibernate as a jpa impl. I am using spring data for crud operations. But in our system our entity object is being pulled/updated many times and there is high contention(concurrency) to data. From our code in many places many classes just inject the service bean and call getEntity method to get an entity, after it they change the entity(which is detached as my understanding, but in the same thread so em object should be the same as per my knowledge) so it takes for a while the entity comes back to service to be updated, they call save() method of the service to save an entity. Save() method is nothing but just calls merge() crud operation. it is transactional with @Transactional annotation. Here is a problem arises, when someone pulls an entity object and while changing it someone else might pull and change it and save it back, so my entity read is dirty and if save it, I will override already updated entity. The problem is here we are changing the entity outside of service and calling save back. Here spring data repository classes are DAO layer.

Optimistic lock is one solution but We did not like it for some reasons, so it does not work for us. I am thinking about pessimistic lock. For example, when I get an entity for update by locking, then change it somewhere else in different place and call back(the entity was already locked against update!) does it work? I am not sure if it still EntityManager object there which I used for pulling an entity. If it is there it takes quite long time to pass those 'smart' logics before it gets updated and unlocks it.

Here is simple example code for the scenario:

class SomeEntity {
    Long id;
    String field1;
    String field2;
    String field3;
    String field4;
    String field5;
    String field6;
    String field7;
    //getters and setters, column annotations

}


class SomeEntityServiceImple implemenet SomeEntityService{
      @Transactional
      save(SomeEntity se){
           //call to spring data's merge() method 
      }

      get(Long id){
           //call to spring data's findOne() 
      }

}


//this class might be spring bean, might not be. If it is not I will get SomeEntityService bean from shared appContext
class SomeCrazyClass{
    @Autowired
    SomeEntityService service;
    someMethod(){
       SomeEntity e = service.get(1L);
       //do tons of logic here, change entity object, call some another methods and again, takes 3 seond
      service.save(e); //Probably detached and someone updated this object, probably overriding it.    
    }
}
}

Here I cannot move that tons of logic inside the service layer, it is quite specific and this kind of different logics are in 100s of places.

So is there any way to come around and at least apply pessimistic lock to this situation?

like image 346
Elbek Avatar asked Nov 03 '22 04:11

Elbek


1 Answers

For pessimistic locking, you can try the below code

  • Locking while fetching the entity

    entityManager.find(Entity.class, pk, LockModeType.PESSIMISTIC_WRITE);

  • Applying lock afterwards

    entityManager.lock(entity, LockModeType.PESSIMISTIC_WRITE);

  • Setting lock mode in query

    query.setLockMode(LockModeType.PESSIMISTIC_WRITE);

Else, you can try adding a synchronized method in SomeEntityServiceImpl.

public synchronized void saveEntity(e){
{
Object o = get(id); //-- Fetch most recent entity 
applyChanges(o, e); //-- Applying custom changes
save(o); //-- Persisting entity
}

Therefore, you don't have to move all your logic inside service, but delegate only the database operations. Also, it will be the common place for code alterations without affecting application logic.

like image 126
Nayan Wadekar Avatar answered Nov 10 '22 16:11

Nayan Wadekar