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解決多食物庫存問題


分享到:


相關文章: