day24

1、事務
1.1概是指邏輯上的一組操做,組成這組操做的各個單元,要麼全成功(提交),要麼所有不成功(回滾到開始處)。mysql

1.2管理事務
  1.數據默認支持事務的,可是數據庫默認的事務是一條sql語句(獨佔一個事務),意義不大。
  2.手動控制事務(命令行)!!
     若是但願本身手動控制事務也是能夠的,如下操做:
        start transaction;
    --開啓事務,在這條命令之後的全部sql語句將處在同一個事務中,要麼全成功,要麼所有成功。
      事務中的sql語句在執行時,並無真正意義的修改數據庫的數據。
 commit;
    --提交事務,將整個事務對數據庫的影響一塊兒發生效果。
 rollback;
     --回滾事務,將這個事務對數據庫的影響取消掉。
  3.JDBC中控制事務!!!
     當JDBC程序向數據庫獲取到Connection對象時,默認狀況下,這個Connection對象會自動提交事務,
     向數據庫提交在它上面的sql語句。若想關閉默認提交方式,讓多條sql語句在同一個事務中執行,能夠
     使用一下語句:
         conn.setAutoCommit(false);
  -取消自動提交方式,變爲手動提交。
  conn.commit();
   --提交事務
  也能夠設置回滾點(還原點),實現回滾部分事務。
  Savepoint sp = conn.setSavepoint();sql

  conn.rollback();
  --回滾事務,回滾事務開啓以前的地方。
  conn.rollback(sp);
  --回滾事務,回滾到sp(回滾點)。
    其餘的部分事務,要想生效還須要執行提交:conn.commit();
   數據庫

1.3事務的四大特性!!!
事務的四大特性是事務自己具備的特色。簡稱ACID。
原子性(Atomicity)
    原子性是指事務是一個不可分割的工做單位,事務中的操做要麼都發生,要麼都不發生。
一致性(Consistency)
    事務先後數據的完整性必須保持一致。
隔離性(Isolation)
    事務的隔離性是指多個用戶併發訪問數據庫時,一個用戶的事務不能被其它用戶的事務所幹擾,
 多個併發事務之間數據要相互隔離。
持久性(Durability)
   持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即便數據庫
 發生故障也不該該對其有任何影響。
1.四、隔離性!!!
數據庫的其餘三大特性數據庫能夠幫咱們保證,而隔離性咱們須要再討論。安全

若是咱們是數據庫的設計者,該如何考慮設計數據庫保證數據庫的隔離性呢?服務器

咱們知道數據庫的隔離性問題本質上就是多線程併發安全性問題。
            性能和安全session

能夠用鎖來解決多線成併發安全問題,可是若是用了鎖,必然會形成程序的性能大大的降低.對於數據庫
這種高併發要求的程序來講這是不可接受的.多線程

咱們能夠具體分析下隔離性產生的細節:併發

   若是兩個線程併發修改,必然產生多線程併發安全問題,必須隔離開
   若是兩個線程併發查詢,必然沒有問題,不須要隔離,
   若是一個線程修改,一個線程查詢,在不一樣的應用場景下有可能有問題,有可能沒問題。
1.5.隔離性可能形成的問題
1.5.1. 髒讀:
一個事務讀取到另一個事務「未提交」的數據。
//數據還原
update account set money = 1000 where 1=1;
//設置當前會話的隔離級別
set session transaction isolation level read uncommitted;


1.5.2.不可重複讀
   一個事務屢次讀取數據庫中的同一條記錄,屢次查詢的結果不一樣(一個事務讀取到另一個已經提交
   的數據。)
  數據庫設計

1.6數據庫的隔離級別
那麼數據庫設計者在設計數據庫時到底該防止哪些問題呢?防止的問題越多性能越低,防止的問題越少,
則安全性越差。
到底該防止哪些問題應該由數據庫使用者根據具體的業務場景來決定,因此數據庫的設計者並無把
放置哪類問題寫死,而是提供了以下選項:
數據庫的四大隔離級別:
read uncommitted;
--- 不作任何隔離,可能形成髒讀 不可重複度 虛讀(幻讀)問題
read committed;
-- 能夠防止髒讀,可是不能防止不可重複度 虛讀(幻讀)問題
repeatable read;
-- 能夠防止髒讀 不可重複度,可是不能防止 虛讀(幻讀)問題
serializable;
 -- 能夠防止全部隔離性的問題,可是數據庫就被設計爲了串行化的數據庫,性能很低
從安全性上考慮:
serializable > repeatable read > read committed > read uncommitted
從性能上考慮:
read uncommitted > read committed > repeatable read > serializable高併發

咱們做爲數據庫的使用者,綜合考慮安全性和性能,從四大隔離級別中選擇一個在能夠防止
想要防止的問題的隔離級別中性能最高的一個.
其中serializable性能過低用的很少,read uncommitted安全性過低用的也很少,
咱們一般從repeatable read和read committed中選擇一個.
若是須要防止不可重複讀選擇repeatable read,若是不須要防止選擇read committed
   
mysql數據庫默認的隔離級別就是repeatable read
Oracle數據庫默認的隔離級別是read committed


1.7.操做數據庫的隔離級別
1.7.1. 查詢數據庫的隔離級別
select @@tx_isolation;
1.7.2. 修改數據庫的隔離級別
set [session/global] transaction isolation level xxxxxx;
不寫默認就是session,修改的是當前客戶端和服務器交互時是使用的隔離級別,並不會影響其
他客戶端的隔離級別
若是寫成global,修改的是數據庫默認的隔離級別(即新開客戶端時,默認的隔離級別),並不會
修改當前客戶端和已經開啓的客戶端的隔離級別
1.8.數據庫中的鎖:
1.8.1.共享鎖
共享鎖和共享鎖能夠共存,共享鎖和排他鎖不能共存.
在非Serializable隔離級別下作查詢不加任何鎖,在Serializable隔離級別下作查詢加共享鎖.
演示:共享鎖和共享鎖能夠共存,共享鎖和排他鎖不能共存.
分別在兩個數據庫客戶端執行如下命令:
set session transaction isolation level serializable;
start transaction;
select * from account;--在兩個事物未提交以前,均可以查詢出結果,說明:共享鎖和共享能夠共存。

在其中一個客戶端中執行:
  update account set money= 900 where name='a';
  當按下回車鍵以後,出現執行等待。。。而後在另一個客戶端中執行:
  commit;
  以後,發現當前客戶端的修改操做接着執行完成。
  說明:共享鎖和排他鎖不可共存。

1.8.2.排他鎖
排他鎖和共享鎖不能共存,排他鎖和排他鎖也不能共存(serializable),在任何隔離級別下作增刪改都加排他鎖.
1.8.3. 可能的死鎖
mysql能夠自動檢測到死鎖,錯誤退出一方執行另外一方
在1.8.1的案例演示基礎上(兩個客戶端的隔離界別都是serializable)
兩個客戶端:
start transaction;
select * from account;
-----------------
其中一個客戶端執行:
  update account set money = 800;
另一個客戶端執行:
  update account set money = 900;

1.9 更新丟失!!!+
1.9.1概念
兩個併發的事務基於同一個查詢的結果進行修改,後提交的事務忽略了先提交的事務對數據的影響,形成了
先提交的事務對數據影響的丟失,這個過程被稱爲更新丟失。
更新丟失問題的產生:
1.遊戲平臺的開發,靠充值掙錢,支付模塊:
分析圖見:更新丟失-悲觀鎖-遊戲平臺在線支付

1.9.2更新丟失的解決方案!!!
將數據的隔離級別設置爲serializable,能夠直接避免該問題的發生。可是咱們通常不會將數據庫的
隔離級別設置serializable。因此該解決方案不多使用。

那麼在非serializable隔離級別下時,如何解決更新丟失的問題?可使用樂觀鎖和悲觀鎖。
樂觀鎖和悲觀鎖並非數據庫中的真實存在的,而是這兩種解決方案的名稱。
(1)悲觀鎖:悲觀的認爲每一次修改,都會形成更新丟失的問題。
    在查詢時,手動的加排他鎖,從而在查詢時就排除可能的更新丟失。
    select * from orders where id = 88 for update;
(2)樂觀鎖
在表設計時,添加一個版本的字段,在進行修改時,要求根據查詢出的版本信息進行修改,並將版本字段
+1,若是更新失敗,說明更新丟失,須要從新進行更新。
分析圖見:更新丟失-樂觀鎖-東方不敗


總結:兩種解決方案各有優缺點,若是查詢多修改少,用樂觀鎖;若是修改多查詢少,使用悲觀鎖。

2、升級EasyMall
2.1升級訂單添加模塊,完成事務版
1修改業務成的訂單添加的方法:
public void addOrder(Order order, List<OrderItem> itemList) {
//添加訂單
Connection conn = null;
try {
 conn = DbUtils.getConn();
 conn.setAutoCommit(false);//開啓事務
 orderDao.addOrder(conn,order);
 for (OrderItem item : itemList) {
  //查詢對應的商品信息
  Product prod = prodDao.findProdById(conn,item.getProduct_id());
  //檢查商品庫存是否充足
  if(prod.getPnum()>=item.getBuynum()){
   //扣掉本次購買
   prodDao.updatePnum(conn,item.getProduct_id(),
     prod.getPnum()-item.getBuynum());
   //向orderitem表中添加一條記錄
   orderDao.addOrderItem(conn,item);
  }else{//庫存不足
   throw new MsgException("商品庫存不足,商品id:"+item.getProduct_id()+",商品名稱:"+prod.getName());
  }
 }

二、在dao層重載用到四個方法(分別在**Dao和**DaoImpl進行重載)
  OrderDao和OrderDaoImpl
addOrder(conn,order)
orderDao.addOrderItem(conn,item);
  ProductDao和ProductDaoImpl

findProdById(conn,item.getProduct_id())
prodDao.updatePnum(conn,item.getProduct_id(),

三、修改DbUtils類,在該類中重載update和query方法
注意:update和query方法中千萬不要關閉數據庫鏈接對象conn
public static <T> T query(Connection conn,String sql, ResultSetHandler<T> rsh,
   Object... params){
 PreparedStatement ps = null;
 ResultSet rs = null;
 try {
  //獲取數據庫鏈接
  //預編譯sql語句並返回PreparedStatement對象
  ps = conn.prepareStatement(sql);
  //爲佔位符賦值
  for(int i = 0;i<params.length;i++){//佔位符設置下標從1開始
   ps.setObject(i+1, params[i]);
  }
  //執行查詢操做
  rs = ps.executeQuery();
  return rsh.handler(rs);
 } catch (Exception e) {
  e.printStackTrace();
  throw new RuntimeException(e);
 }finally{
  //關閉數據鏈接釋放資源
  close(rs, ps, null);//千萬不要關閉數據庫鏈接
 }
}
public static int update(Connection conn,String sql, Object... params){
 PreparedStatement ps = null;
 try {
  //獲取數據庫鏈接
  //預編譯sql語句並返回PreparedStatement對象
  ps = conn.prepareStatement(sql);
  //爲佔位符賦值
  for(int i = 0;i<params.length;i++){
   ps.setObject(i+1, params[i]);
  }
  //執行操做,並返回影響的行數
  return ps.executeUpdate();
 } catch (SQLException e) {
  e.printStackTrace();
  throw new RuntimeException(e);
 }finally{
  close(ps,null);//千萬不要關閉數據庫鏈接
 }
 
}
2.2.事務的解耦合
歸屬於dao層的對象conn,目前用在了業務層,如何解耦?
將conn對象交給第三方區管理,業務層將看不到conn對象。


3、ThreadLocal本地線程變量(重點!!!)
在線程的內部保存數據,利用線程對象在線程執行的過程當中傳遞數據(conn對象),另外
因爲每個線程對象保存各自的數據庫鏈接對象conn,因此對其中一個線程中conn對象的close()
不會影響其餘線程中的conn對象,也就解決了線程併發安全的問題。

是一種數據傳遞的機制。
void set(T value):向當前線程中保存對象
 T get() 返回本地線程變量中的對象(以前保存的對象)。 若是獲取不到對象,則調用initialValue()
 建立一個新的對象。
 T initialValue() 返回本地線程變量中初始化對象。
 void remove() 從本地線程變量中將保存的對象刪除。
 使用ThreadLocal升級代碼
修改TransManager
------------

 總結:因爲每一個線程都有各自的本地線程變量(又保存了本身各自的數據庫鏈接對象conn),全部
 能夠防止多線程併發安全的問題。

目前,程序還存在如下兩個問題:
一、在須要事務的業務層添加事務時,處理事務的代碼比較麻煩
二、調用dao層的方式時,使用事務和不使用事務的方法相似,可是咱們須要
添加不少相似方法。
若是想解決以上兩個問題,須要使用註解和動態代理。
@Tran

預習、註解和動態代理

相關文章
相關標籤/搜索