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.
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.
"@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.
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.
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".
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)...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With