Reputation: 3484
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?
Upvotes: 5
Views: 2552
Reputation: 11991
Move the @Transactional annotation to your upper layer to "someMethod" of "SomeCrazyClass". The changes made withing this method must be a part of one transactional flow. As far as I get it, this flow should be atomic, meaning it should all be committed or non of it (complete rollback).
Putting the @Transactional annotation above a degenerated save method alone is missing the point of this annotation. You could still leave it there (with the propagation.required default value), but you sure need to add it one level up as well.
You should follow this suggestion across your app. Meaning that you should wrap your business logic methods with the Transactional annotation.
Upvotes: -1
Reputation: 11602
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.
Upvotes: 3