Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Optimistic Locking:How to retry transactional method till commit is successful

Tags:

I use Spring 2.5 and Hibernate JPA implementation with Java and "container" managed Transactions.

I have a "after user commit" method that updates data in background and need to be committed regardless of ConcurrencyFailureException or StaleObjectStateException exception, because it will never be shown to client. In other words, need to make Optimistic Lock to Pessimistic. (Could happen if methods execution will take little bit longer and someone changed data in other transaction)


I read a a lot about idempotent stuff, retry if exception in search for DEFAULT_MAX_RETRIES or 6.2.7. Example or chapter 14.5. Retry. I also found in stackoverflow here and here.

I tried this:

public aspect RetryOnConcurrencyExceptionAspect {      private static final int DEFAULT_MAX_RETRIES = 20;     private int maxRetries = DEFAULT_MAX_RETRIES;      Object around(): execution( * * (..) ) && @annotation(RetryOnConcurrencyException) && @annotation(Transactional) {          int numAttempts = 0;           RuntimeException failureException = null;           do {                 numAttempts++;                 try {                     return proceed();                  }                  catch( OptimisticLockingFailureException ex ) {                     failureException = ex;                 }                 catch(ConcurrencyFailureException ex) {                     failureException = ex;                 }                 catch( StaleObjectStateException ex) {                     failureException = ex;                 }           } while( numAttempts <= this.maxRetries );           throw failureException;      } } 

RetryOnConcurrencyException is my Annotation to mark methods that need to be retried, if a exception occurrs. Didn't work... I also tried several ways like SELECT ... FOR UPDATE, EntityManager.lock(...)

What is the best way to avoid stale data, dirty reads etc. such a strategy with Spring? Retry?, synchronized?, JPA lock?, isolation?, select ... for update? I could not get it to work and I really happy about any help.


Here is some pseudo code what I like to do:

void doSomething(itemId) {     select something into A;     select anotherthing into B;      // XXX     item = getItemFormDB( itemId ); // takes long for one user and for other concurrent user it could take less time     item.setA(A);     item.setB(B);      // YYYY     update item;  } 

Between // XXX and // YYY another session could modify the item, then the StaleObjectStateException gets thrown.

like image 222
knarf1983 Avatar asked Jun 29 '10 19:06

knarf1983


People also ask

How do you handle optimistic lock exception?

Solution. To resolve this error we have two ways: Get the latest object from the database and set the old object values if you need those values to be persisted to the new object and merge it. For the old object set the latest version from Database.

Does @transactional lock table Spring?

"@Transactional" as itself on any isolation level doesn't enabling any locking. To achieve locking behaviour you should use "@Lock" annotation or use " for update" in your query.

How does @transactional works in Spring boot?

So when you annotate a method with @Transactional , Spring dynamically creates a proxy that implements the same interface(s) as the class you're annotating. And when clients make calls into your object, the calls are intercepted and the behaviors injected via the proxy mechanism.

What does the @transactional annotation mean?

The @Transactional annotation is metadata that specifies that an interface, class, or method must have transactional semantics; for example, "start a brand new read-only transaction when this method is invoked, suspending any existing transaction".


1 Answers

I got a solution but I think it's ugly. I catch all RuntimeException and it only works for new transactions. Do you know how to make it better? Do you see any problems?

First, I made an Annotation:

@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RetryingTransaction {      int repeatCount() default 20; } 

Then I made a interceptor like this:

    public class RetryingTransactionInterceptor implements Ordered {       private static final int DEFAULT_MAX_RETRIES = 20;       private int maxRetries = DEFAULT_MAX_RETRIES;       private int order = 1;        @Resource       private PlatformTransactionManager transactionManager;        public void setMaxRetries(int maxRetries) {           this.maxRetries = maxRetries;       }       public int getOrder() {           return this.order;       }       public void setOrder(int order) {           this.order = order;       }        public Object retryOperation(ProceedingJoinPoint pjp) throws Throwable {           int numAttempts = 0;           Exception failureException = null;           do {                 numAttempts++;                 try {                     DefaultTransactionDefinition def = new DefaultTransactionDefinition();                     def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);                     TransactionStatus status = transactionManager.getTransaction(def);                      Object obj = pjp.proceed();                      transactionManager.commit(status);                            return obj;                 }                  catch( RuntimeException re ) {                     failureException = re;                 }           } while( numAttempts <= this.maxRetries );           throw failureException;       } } 

Spring applicationConfig.xml:

<tx:annotation-driven transaction-manager="transactionManager" order="10" />  <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">     <property name="transactionSynchronizationName">         <value>SYNCHRONIZATION_ALWAYS</value>     </property> </bean>  <bean id="retryingTransactionInterceptor" class="com.x.y.z.transaction.RetryingTransactionInterceptor">     <property name="order" value="1" /> </bean>  <aop:config>     <aop:aspect id="retryingTransactionAspect" ref="retryingTransactionInterceptor">         <aop:pointcut              id="servicesWithRetryingTransactionAnnotation"              expression="execution( * com.x.y.z.service..*.*(..) ) and @annotation(com.x.y.z.annotation.RetryingTransaction)"/>         <aop:around method="retryOperation" pointcut-ref="servicesWithRetryingTransactionAnnotation"/>     </aop:aspect> </aop:config> 

And a method annotated like this:

@RetryingTransaction public Entity doSomethingInBackground(params)... 
like image 89
knarf1983 Avatar answered Sep 18 '22 16:09

knarf1983