Hibernate(48)Hibernate的乐观锁和悲观锁有什么区别?

7 阅读3分钟

在数据库并发控制中,乐观锁和悲观锁是两种处理并发访问的策略。Hibernate支持这两种锁机制,用于确保数据的一致性和完整性。以下是对这两种锁机制的详细解释及其代码示例。

乐观锁(Optimistic Locking)

乐观锁假设数据并发冲突的概率较低,因此不会在数据库层面上锁住数据,而是通过版本号或时间戳等机制来检测并发冲突。如果在提交更新时发现数据已被其他事务修改,则会报错并回滚当前事务。

使用版本号实现乐观锁

示例代码
  1. 实体类
import javax.persistence.*;

@Entity
@Table(name = "product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "price")
    private Double price;

    @Version
    @Column(name = "version")
    private int version;

    // Getters and Setters
}

在这个例子中,@Version注解用于指定版本字段。每次更新该实体时,Hibernate会自动增加版本号,从而检测并发冲突。

  1. 测试乐观锁
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class OptimisticLockingTest {
    private static final SessionFactory sessionFactory;

    static {
        try {
            sessionFactory = new Configuration().configure("hibernate.cfg.xml").buildSessionFactory();
        } catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static void main(String[] args) {
        // 插入一个产品
        insertProduct("Laptop", 1000.0);

        // 开始两个并发事务
        new Thread(() -> updatePrice(1L, 1200.0)).start();
        new Thread(() -> updatePrice(1L, 1300.0)).start();
    }

    private static void insertProduct(String name, Double price) {
        Session session = sessionFactory.openSession();
        Transaction transaction = session.beginTransaction();
        try {
            Product product = new Product();
            product.setName(name);
            product.setPrice(price);
            session.save(product);
            transaction.commit();
            System.out.println("Product " + name + " inserted");
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            e.printStackTrace();
        } finally {
            session.close();
        }
    }

    private static void updatePrice(Long productId, Double newPrice) {
        Session session = sessionFactory.openSession();
        Transaction transaction = session.beginTransaction();
        try {
            Product product = session.get(Product.class, productId);
            product.setPrice(newPrice);
            session.update(product);
            transaction.commit();
            System.out.println("Product price updated to " + newPrice);
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            e.printStackTrace();
        } finally {
            session.close();
        }
    }
}

在这个示例中,两条线程尝试同时更新产品的价格。如果其中一个线程在提交时发现产品的版本号已被另一线程更新,将会抛出OptimisticLockException,并回滚事务。

悲观锁(Pessimistic Locking)

悲观锁假设数据并发冲突的概率较高,因此会在读取数据时就锁住数据,防止其他事务修改。悲观锁通常在数据库层面上实现,通过锁表或锁行来实现。

使用悲观锁

示例代码
  1. 实体类
import javax.persistence.*;

@Entity
@Table(name = "product")
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "price")
    private Double price;

    // Getters and Setters
}
  1. 测试悲观锁
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

public class PessimisticLockingTest {
    private static final SessionFactory sessionFactory;

    static {
        try {
            sessionFactory = new Configuration().configure("hibernate.cfg.xml").buildSessionFactory();
        } catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static void main(String[] args) {
        // 插入一个产品
        insertProduct("Laptop", 1000.0);

        // 开始两个并发事务
        new Thread(() -> updatePricePessimistically(1L, 1200.0)).start();
        new Thread(() -> updatePricePessimistically(1L, 1300.0)).start();
    }

    private static void insertProduct(String name, Double price) {
        Session session = sessionFactory.openSession();
        Transaction transaction = session.beginTransaction();
        try {
            Product product = new Product();
            product.setName(name);
            product.setPrice(price);
            session.save(product);
            transaction.commit();
            System.out.println("Product " + name + " inserted");
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            e.printStackTrace();
        } finally {
            session.close();
        }
    }

    private static void updatePricePessimistically(Long productId, Double newPrice) {
        Session session = sessionFactory.openSession();
        Transaction transaction = session.beginTransaction();
        try {
            // 使用悲观锁锁定产品行
            Product product = session.get(Product.class, productId, LockMode.PESSIMISTIC_WRITE);
            System.out.println("Product " + productId + " locked");

            product.setPrice(newPrice);
            session.update(product);
            transaction.commit();
            System.out.println("Product price updated to " + newPrice);
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            e.printStackTrace();
        } finally {
            session.close();
        }
    }
}

在这个示例中,两条线程同时尝试锁定和更新产品的价格。由于使用了悲观锁,一条线程会在另一条线程释放锁之前被阻塞,从而确保数据的一致性。

乐观锁和悲观锁的选择

  • 乐观锁
    • 优点:
      • 性能较高,因为不会在数据上加锁。
      • 适用于读多写少的场景。
    • 缺点:
      • 可能会导致较多的重试和失败。
  • 悲观锁
    • 优点:
      • 可以确保数据的一致性,防止并发修改。
      • 适用于写多的场景。
    • 缺点:
      • 性能较低,因为会阻塞其他事务访问被锁定的数据。
      • 可能会导致死锁。

总结

  1. 乐观锁通过版本号或时间戳等机制检测并发冲突,适合读多写少的应用场景。
  2. 悲观锁通过数据库层面的锁来防止并发修改,适合写多的应用场景。

根据应用的具体需求和并发访问模式,选择合适的锁机制可以有效地提高性能并确保数据的一致性。希望这些详细的解释和代码示例能帮助您更好地理解和应用Hibernate的乐观锁和悲观锁。