Pages

30/08/2023

[Spring] Synchronized methods and separate transactions

When marking a method as synchronized, it's important to remember that synchronization does NOT change the transactionality settings of the current operation.

For example the following parallel execution:

methodA() calls syncMethod() then calls somethingElse()
methodB() calls syncMethod() then calls somethingElse()

What happens:
  1. methodA opens Transaction A enters syncMethod
  2. methodB opens Transaction B waits on syncMethod
  3. methodA exits syncMethod, starts executing somethingElse
  4. methodB enters syncMethod while methodA completes somethingElse and commits Transaction A
  5. methodB exits syncMethod, starts executing somethingElse
  6. methodB completes somethingElse and commits Transaction B

Now the important part is points 4. If the syncMethod requires fresh data from other executions, methodB will NOT see that data when it enters syncMethod since Transaction A has NOT yet been commited.

The synchronization is only blocking a thread from entering the protected code block, but as soon as the resource is free, the next waiting thread will enter immediately.

This means that to allow other methods to read latest data while in the protected section, any operation done in the synchronized method must be executed in a separate transaction, for example using the TransactionHandler helper.

09/08/2023

[Spring] Execute method in separate transaction

In Spring annotating a method as requiring a new transaction will not work if the caller of the method is in the same class.

We can easily work around this issue by creating a new class that will execute a given method in a new transaction:

 import org.springframework.stereotype.Service;  
 import org.springframework.transaction.annotation.Propagation;  
 import org.springframework.transaction.annotation.Transactional;  
   
 import java.util.function.Supplier;  
   
 /**  
  * Since spring ignores transaction settings for methods within the same class, we need a separate service  
  * to run isolated transactions which can be called from anywhere simply by supplying the method to execute  
  */  
 @Service  
 public class TransactionHandlerService {  
   
   /**  
    * Runs the given method in a the same transaction as the caller  
    *  
    * @param supplier the method to execute  
    * @param <T>  
    * @return the result of the invoked method  
    */  
   @Transactional(propagation = Propagation.REQUIRED)  
   public <T> T runInSameTransaction(Supplier<T> supplier) {  
     return supplier.get();  
   }  
   
   /**  
    * Runs the given method in a separate transaction  
    *  
    * @param supplier the method to execute  
    * @param <T>  
    * @return the result of the invoked method  
    */  
   @Transactional(propagation = Propagation.REQUIRES_NEW)  
   public <T> T runInNewTransaction(Supplier<T> supplier) {  
     return supplier.get();  
   }  
 }  

Then ensure the callers are annotated as @Transactional and simply pass the method to execute as input to our, for example:

transactionHandlerService.runInNewTransaction(() -> myMethod(someInput));