第一类丢失更新
A事务撤销时,把已经提交的B事务的更新数据覆盖了。这种错误可能造成很严重的问题,通过下面的账户取款转账就可以看出来:
时间 |
取款事务A |
转账事务B |
T1 |
开始事务 |
|
T2 |
|
开始事务 |
T3 |
查询账户余额为1000元 |
|
T4 |
|
查询账户余额为1000元 |
T5 |
|
汇入100元把余额改为1100元 |
T6 |
|
提交事务 |
T7 |
取出100元把余额改为900元 |
|
T8 |
撤销事务 |
|
T9 |
余额恢复为1000 元(丢失更新) |
|
A事务在撤销时,“不小心”将B事务已经转入账户的金额给抹去了。
第二类丢失更新
A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失:
时间 |
转账事务A |
取款事务B |
T1 |
|
开始事务 |
T2 |
开始事务 |
|
T3 |
|
查询账户余额为1000元 |
T4 |
查询账户余额为1000元 |
|
T5 |
|
取出100元把余额改为900元 |
T6 |
|
提交事务 |
T7 |
汇入100元 |
|
T8 |
提交事务 |
|
T9 |
把余额改为1100 元(丢失更新) |
|
上面的例子里由于支票转账事务覆盖了取款事务对存款余额所做的更新,导致银行最后损失了100元,相反如果转账事务先提交,那么用户账户将损失100元
解决方法:有两种
第一种:悲观锁(几乎不用,因为效率及慢,影响用户体验)
一个典型的倚赖数据库的悲观锁调用:
select * from account where name="Erica" for update
这条 sql 语句锁定了 account 表中所有符合检索条件( name="Erica" )的记录。 本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
Hibernate 的悲观锁,也是基于数据库的锁机制实现。
下面的代码实现了对查询记录的加锁:
String hqlStr = "from TUser as user where user.name='Erica'";
Query query = session.createQuery(hqlStr);
query.setLockMode("user",LockMode.UPGRADE); // 加锁
List userList = query.list();// 执行查询,获取数据
query.setLockMode 对查询语句中,特定别名所对应的记录进行加锁(我们为TUser 类指定了一个别名"user"),这里也就是对返回的所有 user 记录进行加锁。
观察运行期 Hibernate 生成的 SQL 语句:
select tuser0_.id as id, tuser0_.name as name, tuser0_.group_id as group_id, tuser0_.user_type as user_type, tuser0_.sex as sex from t_user tuser0_ where (tuser0_.name='Erica' ) for update
这里 Hibernate 通过使用数据库的 for update 子句实现了悲观锁机制。
第二种:乐观锁(建议用)
1.在对应的JavaBean中添加一个属性,名称可以是任意的。例如:private Integer version; 提供get和set方法
2.在映射的配置文件中,提供<version name="version"/>标签即可。
原理:在开启一个事物之前,数据库会自动为你添加一个version字段,默认值为0,当一个用户完成了事物并且更新了数据后,数据库会自动把version字段的值
改为1,同时,如果另外一个用户也操作这条数据,那么,当他想保存数据的时候,就会出现版本不对的问题,从而修改失败,必须重新修改。这样一来,就可以避免
丢失更新的问题。