hibernate解决多食物库存问题

一、前言

1. 事务4个特性

1.原子性(atomic),事务必须是原子工作单元;对于其数据修改,要么全都执行,要么全都不执行

2.一致性(consistent),事务在完成时,必须使所有的数据都保持一致状态。

3.隔离性(insulation),由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。

4.持久性(Duration),事务完成之后,它对于系统的影响是永久性的。

2. 事务并发

通常为了获得更好的运行性能,各种数据库都允许多个事务同时运行,这就是事务并发。3. 事务隔离机制

当并发的事务访问或修改数据库中相同的数据时,通常需要采取必要的隔离机制。

解决并发问题的途径是什么?答案是:采取有效的隔离机制。

怎样实现事务的隔离呢?隔离机制的实现必须使用锁

4. 事务并发问题

1.第一类丢失更新:(在秒杀场景会出现问题)

当事务A和事务B同时修改某行的值,

1.事务A将数值改为1并提交

2.事务B将数值改为2并提交。这时数据的值为2,事务A所做的更新将会丢失。

解决办法:对行加锁,只允许并发一个更新事务。(hibernate中的悲观锁,乐观锁)

2.脏读

1.李四的原工资为8000, 财务人员将李四的工资改为了10000(但未提交事务)

2.李四读取自己的工资 ,发现自己的工资变为了10000,欢天喜地!(在缓存中读取)

3.而财务发现操作有误,回滚了事务,张三的工资又变为了8000 像这样,张三记取的工资数10000是一个脏数据。

解决办法:如果在第一个事务提交前,任何其他事务不可读取其修改过的值,则可以避免该问题。

3.虚读

目前工资为5000的员工有20人。

1.事务1,读取所有工资为5000的员工。

2.这时事务2向employee表插入了一条员工记录,工资也为5000

3.事务1再次读取所有工资为5000的员工共读取到了11条记录,

解决办法:如果在操作事务完成数据处理之前,任何其他事务都不可以添加新数据,则可避免该问题。

4.不可重复读

在一个事务中前后两次读取的结果并不致,导致了不可重复读。

1.在事务1中,Mary 读取了自己的工资为1000,操作并没有完成

2.在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.

3.在事务1中,Mary 再次读取自己的工资时,工资变为了2000

解决办法:如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。

5.第二类丢失更新

多个事务同时读取相同数据,并完成各自的事务提交,导致最后一个事务提交会覆盖前面所有事务对数据的改变

5. 悲观锁乐观锁介绍

1. 悲观锁

如果使用了悲观锁(加了一个行锁),如果事务没有被释放,就会造成其他事务处于等待,所以这里不使用悲观锁。

使用数据库提供的锁机制实现悲观锁。

如果数据库不支持设置的锁机制,hibernate会使用该数据库提供的合适的锁机制来完成,而不会报错。

使用session.load(class,id,LockOptions);加悲观锁,相当于发送SELECT ... FOR UPDATE

使用session.get(class,id,LockOptions);加悲观锁,相当于发送SELECT ... FOR UPDATE

使用session.buildLockRequest(LockOptions).lock(entity);加悲观锁,相当于发送SELECT id

FROM ... FOR UPDATE

使用query.setLockOptions(LockOptions);加悲观锁,相当于发送SELECT... FRO UPDATE

2.乐观锁

推荐使用 version方式;

version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。

当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功

二、代码测试

1. 控制库存不能为负

//控制不能卖出负数的产品mysql不支持check约束,不能通过数据库控制控制负数 public void setNum(Integer num) { if (num < 0) { throw new RuntimeException("库存不够,请刷新再购买"); } t his.num = num; }

2.没有加乐观锁

1.多事务顺序执行。假设总库存10件

//事务1初始库存10件,卖出去5件,最终库存5件 //事务2卖出8件最终库存‐3件,库存为负数。 @Test public void update() throws Exception { // 事务1 Session session = HibernateUtils.getSession(); session.beginTransaction(); Product product = (Product) session.get(Product.class, 1L); product.setNumber(product.getNumber() ‐ 5); session.update(product); session.getTransaction().commit(); session.close(); // 事务2 Session session2 = HibernateUtils.getSession(); session2.beginTransaction(); Product product2 = (Product) session2.get(Product.class, 1L); product2.setNumber(product2.getNumber() ‐ 8); session2.update(product2); session2.getTransaction().commit(); session2.close(); }

2.多事务交叉执行。

//事务1初始库存10件,卖出去8件,最终库存2件,还没有提交。 //事务2卖出5件最终库存5件,库存为5提交事务后库存为5。 //卖出10件库存卖出13件,剩余库存5件。 @Test public void update2() throws Exception { Session session = HibernateUtils.getSession(); Session session2 = HibernateUtils.getSession(); session.beginTransaction(); session2.beginTransaction(); Product product2 = (Product) session2.get(Product.class, 1L); Product product = (Product) session.get(Product.class, 1L);// 库存都是10 product2.setNumber(product2.getNumber() ‐ 8);// 10‐8=2 product.setNumber(product.getNumber() ‐ 5);// 10‐5=5 session.update(product); session2.update(product2); session2.getTransaction().commit();// 更新为2 session.getTransaction().commit();// 更新为5 session2.close(); session.close(); }

3.从上面看出,没有加乐观锁。无论事务顺序执行还是交叉执行,库存都会出问题。

3. 加了乐观锁

准备工作

//添加一个字段Integer version,不由程序员维护,由hibernate自己维护 private Integer version; 映射文件

实体类代码

public class Product { private Long id; private String name; private Integer num; // 添加一个字段Integer version,不由程序员维护,由hibernate自己维护 private Integer version; public Long getId() { return id; } p ublic void setId(Long id) { this.id = id; } p ublic String getName() { return name; } p ublic void setName(String name) { this.name = name; } p ublic Integer getNum() { return num; } p ublic void setNum(Integer num) { if (num < 0) { throw new RuntimeException("库存不够,请刷新再购买"); } t his.num = num; } @ Override public String toString() { return "Product [id=" + id + ", name=" + name + ", num=" + num + "]"; } }

映射文件代码

测试代码

//假设库存为1。 //事务操作流程:先查询,获取库存,在购买,再更新 //事务1查询select o from table where id=100 num=10 version=0 //事务2查询select o from table where id=100 num=10 version=0 //事务1查询购买8件 //事务2查询购买5件 //事务2更新,提交 库存5件 //Update xxx set version=version+1 num=? //Where id=100 and version=0 //事务1更新,报错,抛出异常 //Update xxx set version=version+1 num=? //Where id=100 and version=0 因为version已经改变 //初始库存10件,卖出去5件,最终库存5件 //如果出现乐观锁异常,就捕获StaleObjectStateException异常 //功能实现。 @Test public void update3() throws Exception { try { Session session = HibernateUtils.getSession(); Session session2 = HibernateUtils.getSession(); session.beginTransaction(); session2.beginTransaction(); Product product2 = (Product) session2.get(Product.class, 1L);// 库存都是1 version=0 Product product = (Product) session.get(Product.class, 1L);// 库存都是1 version=0 product2.setNumber(product2.getNumber() ‐ 1);// 1‐1=0 product.setNumber(product.getNumber() ‐ 1);// 1‐1=0 session.update(product); session2.update(product2); // update Product set version=1, name=?, price=?, number=0 where id=1 and version=0 // 数据库最新是version=1 session2.getTransaction().commit();// 更新为2 // update Product set version=1, name=?, price=?, number=0 where id=1 and version=0 // version=0 数据库最新是version=1 session.getTransaction().commit();// 异常 session2.close(); session.close(); } catch (StaleObjectStateException e) {// 库存已经更新 Session session3 = HibernateUtils.getSession(); Product product3 = (Product) session3.get(Product.class, 1L); System.out.println("库存已经更新,最新库存为:" + product3.getNumber()); session3.close(); }

4. 总结:

以上是hibernate框架使用乐观锁的version方式处理库存不为负的代码测试。谢谢观看。

hibernate解决多食物库存问题


分享到:


相關文章: