Java 中的乐观离线锁模式:掌握数据库事务中的冲突解决
也称为
- 乐观并发控制
乐观离线锁设计模式的意图
Java 中的乐观离线锁模式专门用于管理并发数据修改,无需长时间的数据库锁定,从而提高系统性能和可扩展性。
乐观离线锁模式的详细解释,附带实际案例
现实世界中的例子
想象一个图书馆,多个用户借阅和归还书籍。图书馆使用乐观的方法,而不是在用户浏览或决定是否借阅时锁定每本书。每本书都有一个时间戳或版本号。当用户决定借阅一本书时,他们会检查这本书的版本号。如果与当前版本匹配,则交易继续。如果在此期间另一个用户借阅了这本书,导致版本不匹配,则会通知第一个用户重试。这种方法允许多个用户同时浏览并尝试借阅书籍,从而提高图书馆的效率和用户满意度,而不会锁定整个目录。
通俗地讲
乐观离线锁模式通过允许事务在没有锁的情况下进行来管理并发数据修改,仅在发生冲突时才解决冲突,从而提高性能和可扩展性。
维基百科说
乐观并发控制(OCC),也称为乐观锁定,是一种应用于事务系统(如关系数据库管理系统和软件事务内存)的并发控制方法。
Java 中乐观离线锁模式的编程示例
在本节中,我们将深入探讨在 Java 中乐观离线锁的实际实现。通过遵循这些步骤,您可以确保您的应用程序以最小的开销处理数据冲突和并发。
乐观离线锁模式是一种并发控制方法,它允许多个事务在没有锁的情况下进行,仅在发生冲突时才解决冲突。这种模式适用于冲突事务可能性较低且长时间锁定会损害性能和可扩展性的场景。
首先,我们有一个 `Card` 实体,它表示一张带有金额和版本号的银行卡。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Card {
private long id;
private long personId;
private float sum;
private int version;
}
`CardUpdateService` 类实现了 `UpdateService` 接口,并提供了一个 `doUpdate` 方法来更新 `Card` 实体。`doUpdate` 方法首先检索 `Card` 实体的当前版本。然后,它执行一些业务逻辑来更新 `Card` 中的金额。在更新数据库中的 `Card` 之前,它会检查数据库中 `Card` 的版本是否与它最初检索到的版本相同。如果版本匹配,它将继续进行更新。如果版本不匹配,则意味着另一个事务已在此期间更新了 `Card`,并且它会抛出一个 `ApplicationException`。
@RequiredArgsConstructor
public class CardUpdateService implements UpdateService<Card> {
private final JpaRepository<Card> cardJpaRepository;
@Override
@Transactional(rollbackFor = ApplicationException.class) //will roll back transaction in case ApplicationException
public Card doUpdate(Card card, long cardId) {
float additionalSum = card.getSum();
Card cardToUpdate = cardJpaRepository.findById(cardId);
int initialVersion = cardToUpdate.getVersion();
float resultSum = cardToUpdate.getSum() + additionalSum;
cardToUpdate.setSum(resultSum);
//Maybe more complex business-logic e.g. HTTP-requests and so on
if (initialVersion != cardJpaRepository.getEntityVersionById(cardId)) {
String exMessage = String.format("Entity with id %s were updated in another transaction", cardId);
throw new ApplicationException(exMessage);
}
cardJpaRepository.update(cardToUpdate);
return cardToUpdate;
}
}
在此代码片段中,`CardUpdateService` 类中的 `doUpdate` 方法是乐观离线锁模式的编程示例。它允许在没有锁的情况下更新 `Card` 实体,并通过在更新之前检查 `Card` 的版本来解决冲突。
何时在 Java 中使用乐观离线锁模式
- 当多个事务需要同时访问和修改相同的数据而不会导致数据不一致时。
- 在冲突事务可能性较低的系统中。
- 当您想要避免可能损害性能和可扩展性的长时间锁定时。
乐观离线锁模式 Java 教程
Java 中乐观离线锁模式的实际应用
- 具有高读取、低写入访问模式的 Web 应用程序。
- 分布式系统,其中长时间锁定资源不可行。
- 使用 JPA 或 Hibernate 进行数据持久化的 Java 企业应用程序。
乐观离线锁模式的优缺点
优点
- 减少对资源的锁定需求,从而提高性能。
- 通过允许更多事务同时进行来提高系统可扩展性。
- 通过仅在发生冲突时才处理冲突来简化事务管理。
缺点
- 需要额外的逻辑(版本控制、回滚/重试)来处理冲突,这会使应用程序代码复杂化。
- 如果冲突很常见,可能会导致更多事务重试。
- 不适用于频繁数据修改冲突发生的高冲突场景。
相关的 Java 设计模式
- 悲观离线锁:与乐观离线锁不同,这种模式使用锁在整个事务期间锁定数据,从而防止冲突。它适用于高冲突场景。
- 工作单元:帮助将一组更改管理为单个事务,确保数据完整性。它可以与乐观离线锁一起使用来处理复杂的事务。
- 版本号:乐观离线锁中使用的一种常见技术,通过维护每个数据实体的版本号来检测冲突。