使用JPA和Hibernate重试乐观锁定异常

时间:2013-09-29 08:58:27

标签: java spring hibernate jpa transactions

我在hibernate中遇到了一些脏写问题。我添加了@version字段,以便我可以看到我是否正在写一个过时的表。这意味着我现在有很多锅炉板代码

try {
   tryWriteToTable();
} catch (PersistenceExcepton) {  //subclasss of OptimisiticLockException
   try {
      tryWriteToTable();
   } catch (PersistenceExcepton) {
       //dont try again - something seriously wrong
   }
}  

我正在使用Spring,并想知道是否有任何东西可以让我定义这个模式。如果有异常,可以让我重复的东西。除了Spring之外,我还可以使用其他任何东西来避免所有这些丑陋的锅炉板代码。

我想要这样的东西

@TryTwice
private void tryWriteToTable() ....

由于

2 个答案:

答案 0 :(得分:4)

我在this topic上写了一篇文章,同时还有一个你可以在Maven Central找到的小GitHub project

它基于Spring AOP,它提供了一个@Retry注释来标记您希望在乐观锁定异常上重试的服务。

答案 1 :(得分:1)

我认为这个重试问题没有现成的东西。对于当前的任务,还有一个更好的模式,而不是尝试重做某些东西或捕获PersistenceException对象。

由于您使用的是Spring,并且很可能使用Java Transaction Service (JTA)进行事务处理或在JPA兼容模式下使用Hibernate。因此,会话的持久化上下文将在事务提交后清除。这使您的实体与当前(因此任何)会话分离。

因此,您对给定实体所做的每个更改都不再由会话管理(如果您的实体仍由会话管理,您可以使用session.evict(entity)手动分离它。您可能想要阅读有关已分离的内容Hibernate文档中的对象:Working with detached objects

现在,您只需使用MyEntity currentState = session.get(EntityClass.class, detachedEntity.getId());即可重新加载实体所代表的数据库元素的当前状态。通过检查currentState对象的属性,您可以轻松地测试版本号。如果它不同,则数据库中的状态已更改,您可以测试特殊条件并采取相应措施。

示例

例如,我们使用了一段时间以前实施的内容。我们有一个电子邮件系统向负责人发送某些业务报告。要创建电子邮件,需要5分钟或更长时间。午夜后创建电子邮件会延迟,以免扰乱日常操作(创建这些报告的数据库利用率很高)

所以我们的(未经测试,仅用于说明)代码如下所示:

session.beginTransaction();
EmailTask task = getRandomNextTask(session);
session.getTransaction().commit(); //end transaction, task is detached

prepareEmail(); //takes 5minutes or more

session.beginTransaction();
EmailTask currentState = session.get(EmailTask.class, task.getId());
if(currentState.getVersion() == task.getVersion() || currentState.hasError()) {
     currentState.markDone();
     session.getTransaction().commit();
     sendEmail();
} 
else
    trashEmail();

因此,您可以看到我们首先获得下一个电子邮件任务并开始计算需要一些时间的电子邮件。然后我们检查任务是否未被更改,或者如果某个其他进程已更改它,我们检查该进程是否实际上没有发送电子邮件(因为它导致了错误)。如果是这样,我们将任务标记为已完成并实际发送电子邮件。 (我们假设我们的系统在更改数据库后但在实际发送电子邮件之前不会崩溃。