在数据库并发控制中,乐观锁和悲观锁是两种处理并发访问的策略。Hibernate支持这两种锁机制,用于确保数据的一致性和完整性。以下是对这两种锁机制的详细解释及其代码示例。
乐观锁(Optimistic Locking)
乐观锁假设数据并发冲突的概率较低,因此不会在数据库层面上锁住数据,而是通过版本号或时间戳等机制来检测并发冲突。如果在提交更新时发现数据已被其他事务修改,则会报错并回滚当前事务。
使用版本号实现乐观锁
示例代码
- 实体类
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会自动增加版本号,从而检测并发冲突。
- 测试乐观锁
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)
悲观锁假设数据并发冲突的概率较高,因此会在读取数据时就锁住数据,防止其他事务修改。悲观锁通常在数据库层面上实现,通过锁表或锁行来实现。
使用悲观锁
示例代码
- 实体类
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
}
- 测试悲观锁
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();
}
}
}
在这个示例中,两条线程同时尝试锁定和更新产品的价格。由于使用了悲观锁,一条线程会在另一条线程释放锁之前被阻塞,从而确保数据的一致性。
乐观锁和悲观锁的选择
- 乐观锁:
- 优点:
- 性能较高,因为不会在数据上加锁。
- 适用于读多写少的场景。
- 缺点:
- 可能会导致较多的重试和失败。
- 优点:
- 悲观锁:
- 优点:
- 可以确保数据的一致性,防止并发修改。
- 适用于写多的场景。
- 缺点:
- 性能较低,因为会阻塞其他事务访问被锁定的数据。
- 可能会导致死锁。
- 优点:
总结
- 乐观锁通过版本号或时间戳等机制检测并发冲突,适合读多写少的应用场景。
- 悲观锁通过数据库层面的锁来防止并发修改,适合写多的应用场景。
根据应用的具体需求和并发访问模式,选择合适的锁机制可以有效地提高性能并确保数据的一致性。希望这些详细的解释和代码示例能帮助您更好地理解和应用Hibernate的乐观锁和悲观锁。