You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate/Spring:悲观锁与乐观锁相关技术疑问

Great questions—these are common points of confusion when working with Hibernate and Spring transactions, so let’s break them down one by one.


1. Does @Transactional with an isolation level mean you’re using pessimistic locking?

No, they’re not the same thing.

Transaction isolation levels (like REPEATABLE_READ or READ_COMMITTED) are database-level rules that control how transactions interact with each other in terms of visibility of uncommitted changes. For example, InnoDB uses MVCC (Multi-Version Concurrency Control) to implement REPEATABLE_READ by default, which doesn’t involve explicit locking for most reads.

Pessimistic locking, on the other hand, is an explicit mechanism where you lock a database row (or table) to prevent other transactions from modifying it while your transaction is running. In Hibernate, you’d use @Lock(LockModeType.PESSIMISTIC_WRITE) or session.lock() to trigger this, not just the @Transactional isolation level.

The isolation level sets the baseline for transaction behavior, but pessimistic locking is an optional, targeted tool for high-conflict scenarios.

2. Can you use pessimistic and optimistic locking together?

Yes, but you need to use them intentionally to avoid conflicts.

Optimistic locking relies on a @Version field to detect concurrent modifications (throwing OptimisticLockingFailureException if a version mismatch is found). Pessimistic locking locks the row upfront to prevent concurrent changes. You can combine them in scenarios where:

  • Most operations use optimistic locking for better performance (low conflict, high throughput)
  • A small subset of high-conflict operations (like inventory deduction) use pessimistic locking to avoid retries

For example, you could have an entity with a @Version field, and in a critical service method, use @Lock(PESSIMISTIC_WRITE) to lock the row while updating. Hibernate will still increment the version field when saving, so optimistic lock checks remain valid for other operations. Just make sure your pessimistic lock operations still update the version field to keep the two mechanisms aligned.

There are a few practical reasons:

  • Low concurrency/conflict: Many projects (like internal tools or low-traffic apps) don’t face high enough concurrent modification to warrant optimistic locking. The default transaction isolation level is sufficient to handle rare conflicts.
  • Developer familiarity: Teams often stick to what they know. Pessimistic locking (via select ... for update) feels more intuitive for developers who prefer explicit control, and avoiding the retry logic required for optimistic lock failures simplifies code.
  • Legacy constraints: Older projects may not have been designed with a version field, and modifying database schemas to add one can be a hassle (especially with production data).
  • Use case suitability: Some scenarios (like real-time inventory updates or financial transactions) are inherently high-conflict. Pessimistic locking is a more direct solution here, as it avoids the need to handle retry loops for optimistic lock failures.

That said, optimistic locking is still the right choice for high-throughput, low-conflict scenarios (like user profile updates or product info edits) where locking every row would create unnecessary performance bottlenecks.


Quick Code Examples

Optimistic Locking with @Version

@Entity
public class Product {
    @Id
    private Long id;
    private String name;
    private BigDecimal price;
    
    @Version // Tracks entity version for optimistic locking
    private Integer version;
    
    // Getters and setters
}

@Transactional
public void updateProductPrice(Long productId, BigDecimal newPrice) {
    Product product = productRepository.findById(productId).orElseThrow();
    product.setPrice(newPrice);
    productRepository.save(product); // Hibernate checks version on save
}

Pessimistic Locking with @Lock

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

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void deductStock(Long productId, int quantity) {
    Product product = productRepository.findById(productId).orElseThrow();
    if (product.getStock() >= quantity) {
        product.setStock(product.getStock() - quantity);
        productRepository.save(product);
    } else {
        throw new InsufficientStockException();
    }
}

内容的提问来源于stack exchange,提问作者AntonBoarf

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:22:46