如何使用Spring Data JPA优雅地实现乐观锁和悲观锁

在并发数据库操作领域,处理数据完整性至关重要。 Spring Data 与 JPA(Java Persistence API)集成,提供乐观和悲观锁定机制。

乐观锁: 乐观锁的基本思想是,认为在大多数情况下,数据访问不会导致冲突。因此,乐观锁允许多个事务同时读取和修改相同的数据,而不进行显式的锁定。在提交事务之前,会检查是

否有其他事务对该数据进行了修改。如果没有冲突,则提交成功;如果发现冲突,就需要回滚并重新尝试。

乐观锁通常使用版本号或时间戳来实现。每个数据项都会包含一个表示当前版本的标识符。在读取数据时,会将版本标识符保存下来。在提交更新时,会检查数据的当前版本是否与保存的版本匹配。如果匹配,则更新成功;否则,表示数据已被其他事务修改,需要处理冲突。

乐观锁适用于读操作频率较高、写操作冲突较少的场景。它减少了锁的使用,提高了并发性能,但需要处理冲突和重试的情况。

悲观锁: 悲观锁的基本思想是,在数据访问期间假设会发生冲突,因此在访问数据之前就会对其进行锁定,阻止其他事务对该数据进行修改。

悲观锁使用排他锁(Exclusive Lock)来实现。当一个事务对数据进行修改时,它会请求排他锁,并且其他事务无法获取相同的锁直到该事务释放锁。这样可以确保在任何时候只有一个事务能够修改数据,避免了冲突。

悲观锁适用于写操作频率较高、写操作冲突较多的场景。它确保了数据的一致性和完整性,但可能降低并发性能,因为其他事务需要等待锁的释放。

选择乐观锁还是悲观锁取决于具体的应用场景和并发控制需求。乐观锁适合读多写少、冲突较少的情况,而悲观锁适合写多读少、冲突较多的情况。

Spring Data JPA 乐观锁

@Data
@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Version
    private int version;

    private String name;

    private double price;
}

public interface ProductRepository extends JpaRepository<Product, Long> {}

@Service
public class ProductService {
    @Autowired
    private ProductRepository productRepository;

    public void updatePrice(Long id, double newPrice) {
        Product product = productRepository.findById(id).orElseThrow();
        product.setPrice(newPrice);
        productRepository.save(product);
    }
}

在上面的示例中,当两个线程同时尝试更新同一产品的价格时,第一个线程将成功更新该产品。但第二个线程将失败,因为版本不匹配,抛出ObjectOptimisticLockingFailureException。

updatePrice方法生成的 SQL如下:

SELECT id, name, price, version FROM product WHERE id = ?
UPDATE product SET name = ?, price = ?, version = ? WHERE id = ? AND version = ?

原理如下:

  • 在Product实体的version字段添加@Version注解。
  • 读取操作(如findById)是非阻塞的,可以由多个线程并行完成。他们不检查也不关心版本列。
  • 写入操作(如save)将检查版本列,以确保数据自读取以来未发生更改。如果另一个线程同时更新了数据(因此增加了版本号),则保存操作将失败并显示 ObjectOptimisticLockingFailureException。

如果你想确保读取操作是最新的或在读取时阻止其他操作,则需要采用悲观锁定策略,例如 PESSIMISTIC_READ 或 PESSIMISTIC_WRITE。

Spring Data JPA 悲观锁

@Data
@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private double price;
}

public interface ProductRepository extends JpaRepository<Product, Long> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Optional<Product> findByIdLocked(Long id);
}

@Service
public class ProductService {
    @Autowired
    private ProductRepository productRepository;

    @Transactional
    public void updatePrice(Long id, double newPrice) {
        Product product = productRepository
            .findByIdLocked(id)
            .orElseThrow(EntityNotFoundException::new);
        product.setPrice(newPrice);
    }
}

@Lock(LockModeType.PESSIMISTIC_WRITE) 注解确保在调用 findByIdLocked 时获得写锁。

此处,@Transactional 注释在调用 updatePrice 时启动新事务。如果该方法成功完成,则事务提交,如果抛出异常,则回滚。

生成的SQL:

用锁获取:

SELECT id, name, price FROM product WHERE id = ? FOR UPDATE

updatePrice SQL如下:

UPDATE product SET name = ?, price = ? WHERE id = ?

悲观锁提供了一种通过在事务的整个持续时间内获取锁定来防止并发数据访问冲突的方法。此方法在高争用场景中特别有用。然而,必须意识到死锁的可能性以及对系统吞吐量的影响。正确的事务管理(如 @Transactional 所示)可确保操作的原子性。

结论:

在 Spring Data JPA 的事务管理和数据一致性方面,我们有两种主要的锁定策略可供使用:

  • @Transactional+@Lock(LockModeType.PESSIMISTIC_WRITE):这种组合实现了悲观锁定方法。当使用此配置执行读取操作时,应用程序将锁定数据库中的特定行,以防止其他事务修改它,直到当前事务完成。虽然这确保了严格的一致性并防止冲突,但在某些情况下,由于等待释放锁的时间可能会降低吞吐量。
  • @Version:该注解采用乐观锁定策略。这里,当读取数据时,不应用锁。相反,在尝试更新时,Spring Data JPA 会检查自上次读取以来数据的版本是否已被另一个事务修改。如果发生此类修改,则会抛出 ObjectOptimisticLockingFailureException 。该策略假设冲突很少,并且大多数交易将在不受干扰的情况下进行。

根据特定的用例和性能要求,开发人员可以在悲观锁定和乐观锁定之间进行选择。每种方法都有其独特的优点和挑战。该决定取决于并发数据访问的预期频率以及管理数据一致性所需的严格程度。

文章来源网络,作者:管理,如若转载,请注明出处:https://shuyeidc.com/wp/241756.html<

(0)
管理的头像管理
上一篇2025-04-24 11:51
下一篇 2025-04-24 11:52

相关推荐

  • jsp空间购买和交换数据空间怎么买,有哪些注意事项?

    购买JSP空间时,是否考虑过数据交换空间的性能?简米科技(2003年始创,23年行业沉淀)与酷番云(工信部一类增值电信全牌照)这类持牌自营机房的服务商,能确保数据交换的高效稳定,是值得优先选择的合作伙伴,为什么JSP空间需要搭配独立的数据交换空间从JSP应用特性看数据交换需求JSP基于Java技术,常用于企业级……

    2026-08-11
    0
  • 建网站用香港空间效果怎么样,香港空间稳定吗?

    建网站用香港空间,对于创建网站资产来说,核心价值在于免备案和全球带宽优势,尤其适合外贸、跨境电商和需要快速启动的项目,但你必须权衡国内访问延迟,并选择有资质的服务商以保证资产安全,香港空间的核心优势与适用边界免备案:节省时间就是节省成本国内服务器需要备案,通常需要10到20天,香港空间无需备案,域名解析后即可上……

    2026-08-11
    0
  • Java连接云数据库的方法是什么,如何操作

    Java连接云数据库的核心在于通过JDBC驱动,结合云服务商提供的连接地址、端口、数据库名及认证信息,配置安全策略(如SSL、IP白名单),即可实现稳定高效的远程数据库访问,基础准备:JDBC驱动与依赖管理连接云数据库前,需要确保开发环境具备对应的JDBC驱动,以最常见的MySQL为例,你需要引入mysql-c……

    2026-08-11
    0
  • 建网站公安联网备案必须使用数据码吗,备案流程是什么

    网站备案包括ICP备案和公安联网备案,两者缺一不可,公安联网备案必须使用服务商提供的数据码,选择持有合法资质的服务商是顺利通过备案的前提,为什么网站必须进行公安联网备案根据公安部《计算机信息网络国际联网安全保护管理办法》,网站开通后30日内必须到公安机关办理备案手续,未完成公安备案的网站,面临责令整改、关闭网站……

    2026-08-10
    0
  • 建一个企业网站大概需要多少钱?,怎么收费?

    建网站要多少钱,没有一个固定的数字,几百到几万都可能,但真正的“创建网站资产”绝不仅仅是初次投入的成本,而是基于长期稳定、合规和安全的持续性投入,其中核心取决于你选择了什么样的“地基”来承载你的业务,建站预算的构成与行业基准当你开始规划一个网站,最先面对的就是预算问题,一个常见的误区是只关注网站“看起来”的建造……

    2026-08-10
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注