当多个事务同时访问数据库中的相同数据时,如果没有采取必要的隔离措施,将会导致各种并发问题,这时可以采取悲观锁和乐观锁对其控制。
悲观锁:
悲观锁是在每次操作数据时,都悲观地认为会有其他事务也会来操作同一数据。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁由数据库来实现,在锁定的时间其他事务不能对数据进行存取,这样很有可能造成长时间等待。在Hibernate中,用户可以显式地设定要锁定的表或字段及锁的模式。Hibernate锁模式有如下几种:
- LockMode.NONE:如果缓存中存在对象,直接返回该对象的引用,否则通过select语句到数据库中加载该对象,这是锁模式的默认值。
- LockMode.READ:不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否与数据库中的对象版本一致。
- LockMode.UPGRADE:不管缓存中是否存在对象,总是通过select语句到数据库中加载该对象,如果映射文件中设置了版本元素,就执行版本检查,比较缓存中的对象是否与数据库中的对象版本一致,如果数据库系统支持悲观锁(如Oracle/MySql),就执行select…for update语句,如果不支持(如Sybase),就执行普通select语句。
- LockMode.UPGRADE_NOWAIT:与LockMode.UPGRADE具有同样功能,此外对于Oracle等支持update nowait的数据库,执行select…for update nowait语句,nowait表明如果执行该select语句的事务不能立即获得悲观锁,那么不会等待其他事务释放锁,而是立刻抛出异常。
- LockMode.WRITE:保存对象时会自动使用这种锁定模式,仅供hibernate内部使用,应用程序中不应该使用它。
- LockMode.FORCE:强制更新数据库中对象的版本属性,从而表明当前事务已经更新了这个对象。
设置锁模式的方法:
- 调用Session.load()时指定锁定模式
- 调用Session.lock()
- 调用Query.setLockMode()
乐观锁:
相对于悲观锁,乐观锁认为多个事务同时操作同一数据的情况很少发生,因此乐观锁不做数据库层次上锁定,而是基于数据版本标识实现应用程序级别上的锁定机制,既能保证多个事务的并发操作,又能有效防止第二类丢失更新的发生。
数据版本标识,就是通过为数据表增加一个“version”字段实现。读取数据时,将版本号一同读出,之后更新此数据时,将此版本号加1。在提交数据时,将现有版本号与数据库对应记录的版本号进行对比,如果提交数据的版本号大于数据表中的版本号,则允许更新数据,否则禁止更新数据。
在hibernate中,为乐观锁提供了两种基本版本控制的实现,分别是基于version的实现和基于timestamp的实现。