我使用Spring 2.5和Hibernate JPA实现Java和“容器”管理的事务。
我有一个“用户提交后”方法,可以在后台更新数据,无论ConcurrencyFailureException
或StaleObjectStateException
异常都需要提交,因为它永远不会显示给客户端。换句话说,需要将乐观锁定变为悲观。 (如果方法执行需要更长的时间并且有人在其他事务中更改了数据,则可能发生这种情况)
我读了很多关于幂等的东西,如果在search for DEFAULT_MAX_RETRIES或6.2.7. Example或chapter 14.5. Retry中例外,则重试。我还在stackoverflow here和here中找到了。
我试过了:
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
是我的注释,用于标记需要重试的方法(如果发生异常)。没用......我还试过了SELECT ... FOR UPDATE
,EntityManager.lock(...)
使用Spring避免陈旧数据,脏读等等的最佳方法是什么?重试?,同步?,JPA锁?,隔离?,选择...进行更新?我无法让它工作,我真的很高兴任何帮助。
以下是我喜欢做的一些伪代码:
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;
}
在// XXX和// YYY之间,另一个会话可以修改该项,然后抛出StaleObjectStateException。
答案 0 :(得分:10)
我得到了一个解决方案,但我认为这很难看。我捕获所有RuntimeException,它只适用于新事务。你知道怎么做得更好吗?你看到有什么问题吗?
首先,我做了一个注释:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RetryingTransaction {
int repeatCount() default 20;
}
然后我做了一个这样的拦截器:
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>
这样的方法注释如下:
@RetryingTransaction
public Entity doSomethingInBackground(params)...
答案 1 :(得分:6)
当版本号或时间戳检查失败时(发生乐观锁定),使用Spring Retry重试整个方法。
@Configuration
@EnableRetry
public class FooConfig {
...
}
@Retryable(StaleStateException.class)
@Transactional
public void doSomethingWithFoo(Long fooId){
// read your entity again before changes!
Foo foo = fooRepository.findOne(fooId);
foo.setStatus(REJECTED) // <- sample foo modification
} // commit on method end
Spring Boot应用程序定义了有效的spring-retry版本,因此只需要这样:
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
答案 2 :(得分:2)
我们有这个,我们做的是:
在StaleObjectStateException上,清除操作队列
((EventSource) session).getActionQueue().clear()
并从#2
我们有一个重试计数器,最后会重新抛出异常。
注意:这不是官方支持的方法(Hibernate明确指出抛出异常的会话应该被丢弃而不能重复使用),但这是一个已知的解决方法(使用您无法有选择地删除更新操作但必须清除整个队列的限制。
答案 3 :(得分:-2)
在此处抛出另一个选项:BoneCP(http://jolbox.com)支持在失败时自动重试事务(包括数据库出现故障,网络出现故障等)。