Spring乐观锁:如何重试事务方法直到提交成功
我使用 Spring 2.5 和 Hibernate JPA 实现以及 Java 和“容器”管理事务。
我有一个“用户提交后”方法,可以在后台更新数据,并且无论是否出现 ConcurrencyFailureException 或 StaleObjectStateException 异常都需要提交,因为它永远不会向客户端显示。换句话说,需要将乐观锁定为悲观。 (如果方法执行需要更长的时间并且有人在其他事务中更改了数据,则可能会发生)
我读了很多有关幂等的内容,如果 搜索 DEFAULT_MAX_RETRIES 或 6.2.7。示例或第14.5章。重试。我还在 stackoverflow 此处 和 < a href="http://www.springframework.net/doc-latest/reference/html/dao.html" rel="noreferrer">此处。
我尝试了这个:
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 。
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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
这里抛出另一个选项:BoneCP (http://jolbox.com) 支持在失败时自动重试事务(包括当数据库出现故障、网络故障等时)。
Throwing out another option here: BoneCP (http://jolbox.com) has support to automatically retry transactions upon failure (including when DB goes down, network fails, etc).
我们有这个,我们要做的是:
在 StaleObjectStateException 上,清除操作队列
并从 #2 重试
我们有一个重试计数器,用于在最后重新抛出异常。
注意:这不是官方支持的方法(Hibernate 明确指出抛出异常的会话应该被丢弃而不是重新使用),但它是一种已知的解决方法(限制是您不能有选择地删除更新操作,但必须清除整个队列)。
We have this and what we do is:
On StaleObjectStateException, clear the action queue
and retry from #2
We have a retry counter to re-throw the exception in the end.
NOTE: This is not an officially supported method (Hibernate clearly states that a session which has thrown an exception should be discarded and not re-used), but it's a known work-around (with the limitation that you can't selectively remove the update action, but must clear the whole queue).
我有一个解决方案,但我认为它很丑陋。我捕获了所有 RuntimeException 并且它仅适用于新事务。你知道如何让它变得更好吗?你看到什么问题了吗?
首先,我做了一个注释:
然后我做了一个这样的拦截器:
Spring applicationConfig.xml:
以及一个注释如下的方法:
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:
Then I made a interceptor like this:
Spring applicationConfig.xml:
And a method annotated like this:
当版本号或时间戳检查失败时(发生乐观锁),使用 Spring Retry 重试整个方法)。
配置
使用
项目配置
Spring Boot 应用程序已经定义了有效的 spring-retry 版本,因此只需要这样做:
Use Spring Retry to retry whole method when a version number or timestamp check failed (optimistic lock occurs).
Configuration
Usage
Project configuration
Spring Boot application has defined valid spring-retry version, so only this is required: