我假设您想知道的是如何处理并发,防止在应用程序的两个部分修改并意外覆盖相同数据时可能发生的竞争条件。
你主要有两种策略:悲观锁定和乐观锁定:
悲观锁定
在这里您假设两个线程覆盖相同数据的可能性很高,因此您希望它以透明的方式处理它。要解决这个问题,请将 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.
使用乐观锁,您的程序会运行得更快,但应用程序需要处理一些并发方面,否则这些方面在悲观锁下是透明的:版本实体、捕获异常。
最常用的方法是乐观锁定,因为它似乎在大多数应用程序中都可以接受。使用悲观锁定很容易导致性能问题,特别是当数据访问冲突很少并且可以通过简单的方式解决时。
如果需要,在同一个应用程序中混合使用两种并发处理方法没有任何限制。