【问题标题】:How to implement race condition at database level with Spring and hibernate?如何使用 Spring 和 hibernate 在数据库级别实现竞争条件?
【发布时间】:2014-04-18 12:53:57
【问题描述】:

我有一个银行项目,应该通过并行应用程序中的并行线程更新客户余额。我将客户余额保存在 Oracle 数据库中。我的 java 应用程序将使用 Spring 和 Hibernate 实现。

如何实现并行应用程序之间的竞争条件?我的解决方案应该在数据库级别还是在应用程序级别?

【问题讨论】:

  • 介意我为什么要在这种情况下实施竞争条件?
  • 您知道Oracle(实际上几乎每个RDBMS)都提供ACID guarantees吗?
  • 我的应用程序应该在减少余额之前检查余额是否足够。让我们考虑余额为 100 并且应用程序 A 和 B 想要同时减少余额 100。如果有 1 个应用程序和多个线程,我可以使用 java 并发解决这个问题,但是并行应用程序呢?

标签: java spring oracle hibernate race-condition


【解决方案1】:

我假设您想知道的是如何处理并发,防止在应用程序的两个部分修改并意外覆盖相同数据时可能发生的竞争条件。

你主要有两种策略:悲观锁定和乐观锁定:

悲观锁定

在这里您假设两个线程覆盖相同数据的可能性很高,因此您希望它以透明的方式处理它。要解决这个问题,请将 Spring 事务的隔离级别从默认值 READ_COMMITTED 提高到例如 REPEATABLE_READ,这在大多数情况下应该足够了:

@Transactional(isolation=Isolation.REPEATABLE_READ)
public void yourBusinessMethod {
    ...
}

在这种情况下,如果您在方法的开头读取了一些数据,则可以确定在您的方法执行期间没有人可以覆盖数据库中的数据。请注意,另一个线程仍然可以在您所做的查询中插入额外的记录(称为幻读的问题),但不会更改您已经读取的记录。

如果你想防止幻读,你需要将隔离级别升级到SERIALIZABLE。改进的隔离是以性能为代价的,您的程序运行速度会变慢,并且会更频繁地“挂起”等待程序的其他部分完成。

乐观锁定

在这里,您假设数据访问冲突很少见,并且在极少数情况下,它们很容易被应用程序恢复。在此模式下,您将所有业务方法保持在默认的REPEATABLE_READ 模式。

然后每个Hibernate实体都标有版本列:

@Entity
public SomeEntity {
    ...
    @Version
    private Long version;
}

这样,从数据库中读取的每个实体都使用版本列进行版本控制。当 Hibernate 向数据库中的实体写入更改时,它会检查自上次该事务读取实体以来版本是否增加。

如果是这样,则意味着其他人修改了数据,以及使用陈旧数据做出的决定。在这种情况下,会抛出 StaleObjectException,这需要被应用程序捕获并处理,最好是在中心位置。

在 GUI 的情况下,你通常会捕获异常,显示一条消息说user xyz changed this data while you where also editing it, your changes are lost. Press Ok to reload the new data.

使用乐观锁,您的程序会运行得更快,但应用程序需要处理一些并发方面,否则这些方面在悲观锁下是透明的:版本实体、捕获异常。

最常用的方法是乐观锁定,因为它似乎在大多数应用程序中都可以接受。使用悲观锁定很容易导致性能问题,特别是当数据访问冲突很少并且可以通过简单的方式解决时。

如果需要,在同一个应用程序中混合使用两种并发处理方法没有任何限制。

【讨论】:

  • 我认为我的解决方案应该是乐观交易。谢谢你的建议@jhadesdev
猜你喜欢
  • 2012-04-08
  • 1970-01-01
  • 2017-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-19
  • 1970-01-01
  • 2017-02-02
相关资源
最近更新 更多