Hibernate 乐观锁实现示例

乐观锁是一种并发控制策略,假设多个事务可以同时成功,通过版本号或时间戳来检测冲突。以下是 Hibernate 中乐观锁的完整实现示例。

图片[1]_Hibernate 乐观锁实现示例_知途无界

1. 基于版本号的乐观锁(推荐)​

实体类配置

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "products")
public class Product {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "product_name")
    private String productName;
    
    private Double price;
    
    private Integer stock;
    
    // 版本号字段 - 核心注解
    @Version
    @Column(name = "version")
    private Integer version;
    
    @Column(name = "create_time")
    private LocalDateTime createTime;
    
    @Column(name = "update_time")
    private LocalDateTime updateTime;
    
    // 构造方法
    public Product() {}
    
    public Product(String productName, Double price, Integer stock) {
        this.productName = productName;
        this.price = price;
        this.stock = stock;
        this.createTime = LocalDateTime.now();
        this.updateTime = LocalDateTime.now();
    }
    
    // Getter 和 Setter 方法
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getProductName() { return productName; }
    public void setProductName(String productName) { this.productName = productName; }
    
    public Double getPrice() { return price; }
    public void setPrice(Double price) { this.price = price; }
    
    public Integer getStock() { return stock; }
    public void setStock(Integer stock) { this.stock = stock; }
    
    public Integer getVersion() { return version; }
    public void setVersion(Integer version) { this.version = version; }
    
    public LocalDateTime getCreateTime() { return createTime; }
    public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
    
    public LocalDateTime getUpdateTime() { return updateTime; }
    public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
    
    @PreUpdate
    public void preUpdate() {
        this.updateTime = LocalDateTime.now();
    }
    
    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                ", productName='" + productName + '\'' +
                ", price=" + price +
                ", stock=" + stock +
                ", version=" + version +
                ", createTime=" + createTime +
                ", updateTime=" + updateTime +
                '}';
    }
}

对应的数据库表结构

CREATE TABLE products (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    product_name VARCHAR(255) NOT NULL,
    price DOUBLE NOT NULL,
    stock INT NOT NULL,
    version INT DEFAULT 0,  -- 版本号字段
    create_time DATETIME,
    update_time DATETIME
);

2. 基于时间戳的乐观锁

实体类配置

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "orders")
public class Order {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "order_number")
    private String orderNumber;
    
    @Column(name = "customer_name")
    private String customerName;
    
    private Double amount;
    
    // 时间戳版本控制 - 核心注解
    @Version
    @Column(name = "last_updated")
    private LocalDateTime lastUpdated;
    
    // 构造方法
    public Order() {}
    
    public Order(String orderNumber, String customerName, Double amount) {
        this.orderNumber = orderNumber;
        this.customerName = customerName;
        this.amount = amount;
        this.lastUpdated = LocalDateTime.now();
    }
    
    // Getter 和 Setter 方法
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getOrderNumber() { return orderNumber; }
    public void setOrderNumber(String orderNumber) { this.orderNumber = orderNumber; }
    
    public String getCustomerName() { return customerName; }
    public void setCustomerName(String customerName) { this.customerName = customerName; }
    
    public Double getAmount() { return amount; }
    public void setAmount(Double amount) { this.amount = amount; }
    
    public LocalDateTime getLastUpdated() { return lastUpdated; }
    public void setLastUpdated(LocalDateTime lastUpdated) { this.lastUpdated = lastUpdated; }
    
    @Override
    public String toString() {
        return "Order{" +
                "id=" + id +
                ", orderNumber='" + orderNumber + '\'' +
                ", customerName='" + customerName + '\'' +
                ", amount=" + amount +
                ", lastUpdated=" + lastUpdated +
                '}';
    }
}

3. Repository/DAO 层实现

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import java.util.Optional;

@Repository
@Transactional
public class ProductRepository {
    
    @PersistenceContext
    private EntityManager entityManager;
    
    // 方法1: 使用 JPA 的 save 方法(Spring Data JPA)
    public Product save(Product product) {
        return entityManager.merge(product);
    }
    
    // 方法2: 使用 Hibernate Session 进行更细粒度控制
    public Product updateProductStock(Long productId, Integer quantity) {
        Session session = entityManager.unwrap(Session.class);
        Product product = session.get(Product.class, productId);
        
        if (product != null) {
            product.setStock(product.getStock() - quantity);
            // Hibernate 会自动处理版本号递增
            session.update(product);
        }
        
        return product;
    }
    
    // 方法3: 悲观锁与乐观锁结合(在特定场景下使用)
    public Product updateWithPessimisticLock(Long productId, Integer quantity) {
        Session session = entityManager.unwrap(Session.class);
        
        // 先使用悲观锁锁定记录
        Product product = session.get(Product.class, productId, LockMode.PESSIMISTIC_WRITE);
        
        if (product != null && product.getStock() >= quantity) {
            product.setStock(product.getStock() - quantity);
            // 乐观锁仍然有效,版本号会自动递增
            session.update(product);
            return product;
        }
        
        return null; // 库存不足或商品不存在
    }
}

4. Service 层业务逻辑(演示乐观锁冲突处理)​

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.OptimisticLockException;
import javax.persistence.RollbackException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Service
public class ProductService {
    
    @Autowired
    private ProductRepository productRepository;
    
    // 单线程环境下的正常更新
    @Transactional
    public Product purchaseProduct(Long productId, Integer quantity) {
        Product product = productRepository.updateProductStock(productId, quantity);
        
        if (product != null) {
            System.out.println("购买成功: " + product);
            return product;
        } else {
            throw new RuntimeException("商品不存在或库存不足");
        }
    }
    
    // 演示乐观锁冲突处理
    @Transactional
    public void demonstrateOptimisticLockConflict() {
        Long productId = 1L;
        
        try {
            // 第一次更新 - 应该成功
            Product product1 = productRepository.updateProductStock(productId, 1);
            System.out.println("第一次更新成功,版本号: " + product1.getVersion());
            
            // 模拟在内存中修改但不立即提交的情况
            product1.setPrice(product1.getPrice() * 1.1);
            
            // 假设此时另一个事务已经提交了更新,版本号已改变
            // 这里我们手动模拟版本冲突进行测试
            simulateConcurrentUpdate(productId);
            
            // 第二次更新 - 可能遇到乐观锁冲突
            productRepository.save(product1);
            System.out.println("第二次更新成功");
            
        } catch (OptimisticLockException e) {
            System.err.println("检测到乐观锁冲突: " + e.getMessage());
            // 处理冲突:重试、回滚或通知用户
            handleOptimisticLockException(e);
        } catch (RollbackException e) {
            System.err.println("事务回滚: " + e.getMessage());
        }
    }
    
    // 模拟并发更新导致的版本冲突
    private void simulateConcurrentUpdate(Long productId) {
        // 在实际应用中,这是由另一个线程或事务完成的
        // 这里我们直接查询并更新来模拟
        Product product = productRepository.updateProductStock(productId, 1);
        if (product != null) {
            System.out.println("并发更新成功,新版本号: " + product.getVersion());
        }
    }
    
    // 乐观锁冲突处理策略
    private void handleOptimisticLockException(OptimisticLockException e) {
        // 策略1: 重试机制
        // retryOperation();
        
        // 策略2: 记录日志并通知用户
        System.out.println("数据已被其他用户修改,请刷新后重试");
        
        // 策略3: 抛出异常让上层处理
        throw new BusinessException("操作失败,数据已被其他用户修改,请重试", e);
    }
    
    // 带重试机制的更新操作
    @Transactional
    public Product updateWithRetry(Long productId, Integer quantity, int maxRetries) {
        int retryCount = 0;
        
        while (retryCount < maxRetries) {
            try {
                return productRepository.updateProductStock(productId, quantity);
            } catch (OptimisticLockException e) {
                retryCount++;
                System.out.println("乐观锁冲突,第 " + retryCount + " 次重试");
                
                if (retryCount >= maxRetries) {
                    throw new BusinessException("操作失败,经过 " + maxRetries + " 次重试仍然冲突", e);
                }
                
                // 等待一段时间后重试
                try {
                    Thread.sleep(100 * retryCount); // 递增等待时间
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
        
        return null;
    }
    
    // 使用多线程模拟高并发场景
    public void concurrentPurchaseSimulation(Long productId, int threadCount) {
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        
        CompletableFuture<?>[] futures = new CompletableFuture[threadCount];
        
        for (int i = 0; i < threadCount; i++) {
            final int threadIndex = i;
            futures[i] = CompletableFuture.runAsync(() -> {
                try {
                    Product product = productRepository.updateProductStock(productId, 1);
                    if (product != null) {
                        System.out.println("线程 " + threadIndex + " 购买成功,剩余库存: " + product.getStock());
                    } else {
                        System.out.println("线程 " + threadIndex + " 购买失败");
                    }
                } catch (OptimisticLockException e) {
                    System.out.println("线程 " + threadIndex + " 遇到乐观锁冲突: " + e.getMessage());
                }
            }, executor);
        }
        
        // 等待所有任务完成
        CompletableFuture.allOf(futures).join();
        executor.shutdown();
    }
}

5. 自定义异常类

// 自定义业务异常
public class BusinessException extends RuntimeException {
    
    public BusinessException(String message) {
        super(message);
    }
    
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
}

6. 测试示例

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class OptimisticLockTest {
    
    @Autowired
    private ProductService productService;
    
    @Autowired
    private ProductRepository productRepository;
    
    @Test
    public void testOptimisticLock() {
        // 创建测试产品
        Product product = new Product("iPhone 15", 999.99, 10);
        Product savedProduct = productRepository.save(product);
        System.out.println("初始产品: " + savedProduct);
        
        // 测试正常更新
        productService.purchaseProduct(savedProduct.getId(), 1);
        
        // 测试乐观锁冲突
        productService.demonstrateOptimisticLockConflict();
        
        // 测试并发购买
        productService.concurrentPurchaseSimulation(savedProduct.getId(), 5);
    }
}

7. 配置说明

application.properties 配置

# Hibernate 配置
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto=validate  # 生产环境建议使用 validate

# 乐观锁相关配置
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true

核心要点总结

  1. ​@Version 注解​:在实体类的版本字段上使用 @Version 注解,Hibernate 会自动管理版本号递增和冲突检测。
  2. 冲突检测​:当多个事务同时更新同一数据时,Hibernate 会比较版本号,如果发现版本不匹配,会抛出 OptimisticLockException
  3. 冲突处理策略​:
    • 重试机制​:在捕获异常后重新尝试操作
    • 用户提示​:通知用户数据已被修改,请刷新重试
    • 业务回滚​:记录日志并回滚事务
  4. 适用场景​:乐观锁适合读多写少、冲突较少的场景。在高冲突场景下,可能需要结合悲观锁使用。
  5. 版本字段选择​:
    • ​@Version Integer/Long​:推荐使用,性能好
    • ​@Version Timestamp​:精度可能不够,不推荐用于高并发场景

这种实现方式可以有效防止数据不一致问题,同时保持系统的高并发性能。

© 版权声明
THE END
喜欢就点个赞,支持一下吧!
点赞23 分享
评论 抢沙发
头像
欢迎您留下评论!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容