Spring Data Jpa #Lock annotation with #Transactional - java

As per java docs for #Lock annotations:
Annotation used to specify the LockModeType to be used when executing the query. It will be evaluated when using Query on a query method or if you derive the query from the method name.
As mentioned above it will be evaluated with #Query annotation or findBySomething..() method.
But as per my finding, when i put #Lock annotation with #Transactional in any method, and get record from db in that transaction method it is acquiring lock on db rows, ideally it should not(correct me if i am wrong):
I verified this with two transactions T1 and T2 as follows:
Starts T1 first, and fetch some records from db and sleep that thread and did not update them.
Now start T2 on other method having same #Lock with pessimistic write and #Transactional annotation and fetch same records and trying to update them, But when it is trying to commit those changes it waits for some time and then throw exception saying PessimsticLock timeout exception
#Transactional
#Lock(LockModeType.PESSIMISTIC_WRITE)
public boolean addInventory(Integer id){
repo.findById(id)// #statement 1
}
also when #statement 1 is called it does not fire "select for update" query instead only fire select query.
Is it true that #Lock can be used with #Transactional and all rows which are fetched under this transaction get lock ?
Using SpringBoot version 2.1.4.RELEASE

I was able to figure out the behavior which I was facing:
#Lock annotation has not effect, if it put over method having #Transactional annotation only , it is working with #Query or findByXyz() method.
In my case, T2 transaction not able to commit because of database which is MySQL and have default transaction isolation level 'Repeatable Read' which do not allow to proceed T2 transaction until T1 commits.
#Lock(LockModeType.PESSIMISTIC_WRITE) //will not have any effect
#Transactional
public Response handleRequest(){
//no lock will acquire
// prior to know about mysql's repeatble read, i was having impression, any call to db in this method will lock the db rows, which is wrong
}
#Lock(LockModeType.PESSIMISTIC_WRITE)// will fire select for update
#Transactional
public findByUserId(){
// any db call from repo layer which have no lock annotations
}
#Lock(LockModeType.PESSIMISTIC_WRITE)// will fire select for update
#Query("select u from user as u where u.id=:id")
#Transactional
public getByUserID(String id){
}

Related

Spring JPA PESSIMISTIC_WRITE lock mode does not lock rows when other transactions read the same row

The following is a transaction method that retrieves an existing row from the database and updates one of its column value and then saves back to the database:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public A updateTotalItem(String id, int newItemNum) {
A a = aRepository.findById(id).orElseThrow(() -> new IllegalArgumentException());
a.updateValues(newItemNum);
return aRepository.save(a);
}
And I have applied a PESSIMISTIC_WRITE lock to aRepository.findById():
#Repository
public interface ARepository extends JpaRepository<A, String> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<A> findById(String id);
}
However, I discovered that if there are two concurrent calls to the above transaction method (with the same id argument provided), even though the first transaction will block the update of the second transaction, but after the first transaction committed, the second transaction would not retrieve the row with updated data, instead, it would still get the row with data prior to the first transaction committed. If I understand PESSIMISTIC_WRITE lock correctly, it should lock the row/entity for read/write, but in this case, looks like it only locks it for writing, am I understanding it wrong? If I am, is there any approach I can use to achieve the effect to lock the row/entity for read/write to prevent concurrent transactions getting the old data?
PS: I am using Hibernate as ORM and Postgres as database

How does transaction propagation impact update in the database

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.

why do we have to use #Modifying annotation for queries in Data Jpa

for example I have a method in my CRUD interface which deletes a user from the database:
public interface CrudUserRepository extends JpaRepository<User, Integer> {
#Transactional
#Modifying
#Query("DELETE FROM User u WHERE u.id=:id")
int delete(#Param("id") int id, #Param("userId") int userId);
}
This method will work only with the annotation #Modifying. But what is the need for the annotation here? Why cant spring analyze the query and understand that it is a modifying query?
CAUTION!
Using #Modifying(clearAutomatically=true) will drop any pending updates on the managed entities in the persistence context spring states the following :
Doing so triggers the query annotated to the method as an updating
query instead of selecting one. As the EntityManager might contain
outdated entities after the execution of the modifying query, we do
not automatically clear it (see the JavaDoc of EntityManager.clear()
for details), since this effectively drops all non-flushed changes
still pending in the EntityManager. If you wish the EntityManager to
be cleared automatically, you can set the #Modifying annotation’s
clearAutomatically attribute to true.
Fortunately, starting from Spring Boot 2.0.4.RELEASE Spring Data added flushAutomatically flag (https://jira.spring.io/browse/DATAJPA-806) to auto flush any managed entities on the persistence context before executing the modifying query check reference https://docs.spring.io/spring-data/jpa/docs/2.0.4.RELEASE/api/org/springframework/data/jpa/repository/Modifying.html#flushAutomatically
So the safest way to use #Modifying is :
#Modifying(clearAutomatically=true, flushAutomatically=true)
What happens if we don't use those two flags??
Consider the following code :
repo {
#Modifying
#Query("delete User u where u.active=0")
public void deleteInActiveUsers();
}
Scenario 1 why flushAutomatically
service {
User johnUser = userRepo.findById(1); // store in first level cache
johnUser.setActive(false);
repo.save(johnUser);
repo.deleteInActiveUsers();// BAM it won't delete JOHN right away
// JOHN still exist since john with active being false was not
// flushed into the database when #Modifying kicks in
// so imagine if after `deleteInActiveUsers` line you called a native
// query or started a new transaction, both cases john
// was not deleted so it can lead to faulty business logic
}
Scenario 2 why clearAutomatically
In following consider johnUser.active is false already
service {
User johnUser = userRepo.findById(1); // store in first level cache
repo.deleteInActiveUsers(); // you think that john is deleted now
System.out.println(userRepo.findById(1).isPresent()) // TRUE!!!
System.out.println(userRepo.count()) // 1 !!!
// JOHN still exists since in this transaction persistence context
// John's object was not cleared upon #Modifying query execution,
// John's object will still be fetched from 1st level cache
// `clearAutomatically` takes care of doing the
// clear part on the objects being modified for current
// transaction persistence context
}
So if - in the same transaction - you are playing with modified objects before or after the line which does #Modifying, then use clearAutomatically & flushAutomatically if not then you can skip using these flags
BTW this is another reason why you should always put the #Transactional annotation on service layer, so that you only can have one persistence context for all your managed entities in the same transaction.
Since persistence context is bounded to hibernate session, you need to know that a session can contain couple of transactions see this answer for more info https://stackoverflow.com/a/5409180/1460591
The way spring data works is that it joins the transactions together (known as Transaction Propagation) into one transaction (default propagation (REQUIRED)) see this answer for more info https://stackoverflow.com/a/25710391/1460591
To connect things together if you have multiple isolated transactions (e.g not having a transactional annotation on the service) hence you would have multiple sessions following the way spring data works hence you have multiple persistence contexts (aka 1st level cache) that means you might delete/modify an entity in a persistence context even with using flushAutomatically the same deleted/modified entity might be fetched and cached in another transaction's persistence context already, That would cause wrong business decisions due to wrong or un-synced data.
This will trigger the query annotated to the method as updating query instead of a selecting one. As the EntityManager might contain outdated entities after the execution of the modifying query, we automatically clear it (see JavaDoc of EntityManager.clear() for details). This will effectively drop all non-flushed changes still pending in the EntityManager. If you don't wish the EntityManager to be cleared automatically you can set #Modifying annotation's clearAutomatically attribute to false;
for further detail you can follow this link:-
http://docs.spring.io/spring-data/jpa/docs/1.3.4.RELEASE/reference/html/jpa.repositories.html
Queries that require a #Modifying annotation include INSERT, UPDATE, DELETE, and DDL statements.
Adding #Modifying annotation indicates the query is not for a SELECT query.
When you use only #Query annotation,you should use select queries
However you #Modifying annotation you can use insert,delete,update queries above the method.

update and return data in spring data JPA

For concurrency purpose, I have got a requirement to update the state of a column of the database to USED while selecting from AVAILABLE pool.
I was thinking to try #Modifying, and #Query(query to update the state based on the where clause)
It is all fine, but this is an update query and so it doesn't return the updated data.
So, is it possible in spring data, to update and return a row, so that whoever read the row first can use it exclusively.
My update query is something like UPDATE MyObject o SET o.state = 'USED' WHERE o.id = (select min(id) from MyObject a where a.state='AVAILABLE'), so basically the lowest available id will be marked used. There is a option of locking, but these requires exceptional handling and if exception occur for another thread, then try again, which is not approved in my scenario
You need to explicitly declare a transaction to avoid other transactions being able to read the values involved until it's commited. The level with best performance allowing it is READ_COMMITED, which doesn't allow dirty reads from other transactions (suits your case). So the code will look like this:
Repo:
#Repository
public interface MyObjectRepository extends JpaRepository<MyObject, Long> {
#Modifying
#Query("UPDATE MyObject o SET o.state = 'USED' WHERE o.id = :id")
void lockObject(#Param("id") long id);
#Query("select min(id) from MyObject a where a.state='AVAILABLE'")
Integer minId();
}
Service:
#Transactional(isolation=Isolation.READ_COMMITTED)
public MyObject findFirstAvailable(){
Integer minId;
if ((minId = repo.minId()) != null){
repo.lockObject(minId);
return repo.findOne(minId);
}
return null;
}
I suggest to use multiple transactions plus Optimistic Locking.
Make sure your entity has an attribute annotated with #Version.
In the first transaction load the entity, mark it as USED, close the transaction.
This will flush and commit the changes and make sure nobody else touched the entity in the mean time.
In the second transaction you can no do whatever you want to do with the entity.
For these small transactions I find it clumsy to move them to separate methods so I can use #Transactional. I therefore use the TransactionTemplate instead.

#Transaction - How to commit to another method in same transaction

I Have a method using #Transactional annotation, and inside this method, i have one that persists one entity, and the next ones uses this persisted entity that is not yet persisted, because the method using #Transactional dont finished.
What is the best approach to do this? I Think about REQUIRED_NEW, but when this is a new transactional, if the external transaction fails, it will not fail all.
Thanks !!
Paulo
#Override
#Transactional
public Catalog updateCatalog(CatalogPrice catalog, Long id) {
CatalogEntity catalogEntity = CatalogEntity.findSingle(id);
Catalog catalog = catalogHand.updateCatalogPrice(catalog);
catalogEntity.sendToQueue(catalog);
return catalog;
}
Within the same transaction boundary - any changes you made (CREATE or UPDATE) will be visible. I believe you need to call flush() method between the method calls.
// Create code
entityManager.flush(); // If you use JPA, or it will be session.flush() for hibernate
// Update code goes here
Persistence context is flushed only when you call flush() explicitly or when you search for the entity or when the transaction commits. Only in these cases the changes you made will be available.

Categories

Resources