【问题标题】:How to allow part of a transaction to fail in Spring/Hibernate webapp?如何允许部分事务在 Spring/Hibernate webapp 中失败?
【发布时间】:2014-03-12 22:37:56
【问题描述】:
我的 web 应用程序出现了一种情况,我还没有找到好的解决方案。在我的应用程序中,用户可以对外部(我的应用程序)音乐数据库进行搜索。我代表他们进行搜索,然后展示结果。
然后他们可以选择将这些结果中的一个或多个添加到他们的用户记录中。在内部,我在表中为歌曲创建一行(如果它不在我的数据库中),复制歌曲信息,然后将其添加到他们的收藏中。
问题是两个用户都可能尝试将同一首歌曲添加到数据库(具有唯一性约束)。一个用户会成功,而另一个用户会失败,但直到整个事务结束。如果用户 A 获胜而用户 B 没有获胜,我不希望将歌曲添加到数据库失败;我想捕获该错误,从我的数据库中加载(现在存在的)歌曲,然后继续请求将其添加到用户的收藏中。
根据我在 Hibernate 文档(此处:http://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/transactions.html#transactions-basics-uow)中读到的内容,这不是它的预期用途。当然,我可以让整个事务失败,并将其报告给用户,但是对于应该可以恢复的情况来说,这是一种糟糕的用户体验。
有没有一种简单的方法来处理这个问题?谢谢!
【问题讨论】:
标签:
java
spring
hibernate
transactions
【解决方案1】:
你能同步这些交易吗?研究并发性,因为我认为这就是您要寻找的。p>
【解决方案2】:
先生,您可以在其中使用监视器和信号量的概念。考虑保存为关键区域并将两个线程都通过互斥体传递,因此将只提供给另一个线程的访问权限将不得不等待。
一旦一个完成另一个可以进入关键区域。
【解决方案3】:
有两种方法,要么通过将业务方法的隔离级别提升到SERIALIZABLE来防止错误,要么先检查歌曲是否存在,然后再插入。
没有其他隔离级别就足够了,因为在这种情况下,我们需要防止幻读。
否则可以使用乐观锁定。 Spring 会将 Hibernate 异常转换为 spring OptimisticLockingFailureException。
这可以在调用业务方法的方法中捕获:
@Service
public class SongService {
@Transactional
public void addSongIfNotExists() {
...
}
}
@Service
public class AddSongFailureHandlingService {
@Autowired
private SongService songService;
// NEVER makes sure no transaction is ongoing, that would prevent commit after
// calling addSongIfNotExists
@Transactional(propagation= Propagation.NEVER)
public callingMethod() {
try {
// no transaction is propagated to this method, this will cause a
// commit attempt
songService.addSongIfNotExists();
} catch(OptimisticLockingFailureException exc) {
// transaction rolled back, song already inserted
// apply recovery logic
...
}
}
}
重要的是要知道在事务回滚之后,任何打开的实体管理器/休眠会话都需要被丢弃。
因此,如果您需要在数据库中执行某些操作以从乐观锁定异常中恢复,请确保调用另一个 @Transactional 业务方法,以便获得新的会话/实体管理器。