JDBC5 - 事務

事務

事務指邏輯上的一組操作,組成這組操作的各個單元,要不全部成功,要不全部不成功

數(shù)據(jù)庫開啟事務命令

  • start transaction 開啟事務
  • Rollback 回滾事務
  • Commit 提交事務


    使用事務
//方式一
//創(chuàng)建表
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
insert into account values(null,'aaa',1000);
insert into account values(null,'bbb',1000);
//創(chuàng)建事務
start transaction;
update account money=money-500 where id=1;
update account money=money+500 where id=1;
update account money=money+500 where id=1;
rollback;//事務回滾,回滾到最開始的樣子
commit;//只有commit后,以上才會操作

//方式二 
//可以查看當前autocommit值,默認“ON”打開狀態(tài),任意一條語句都是一個事務
show variable like '%commit%';
//關閉自動事務,需要手動commit
set autocommit = off;
//JDBC方式
public class TransactionTest1 {
    public static void main(String[] args) throws Exception {
        //修改id=2的money
        String sql = "update account set money=100 where id=2";
        Connection con = jdbcUtils.getConnection();
        con.setAutoCommit(false);//開啟事務
        
        Statement st = con.createStatement();
        st.executeUpdate(sql);
        con.rollback();//事務回滾
        
        con.commit();//事務提交
        
        st.close();
        con.close();
    }
}

事務特性

  • 原子性
  • 一致性
  • 隔離性
  • 持久性

事務隔離性的設置語句

設置事務隔離性
  • 臟讀 dirty read 指一個事務讀取了另一個事務未提交的數(shù)據(jù)
  • 不可重復讀 指在一個事務內(nèi)讀取表中的某一行數(shù)據(jù),多次讀取結(jié)果不同(update)
  • 虛讀 指在一個事務內(nèi)讀取到了別的事務插入的數(shù)據(jù),導致前后讀取不一致( insert )
  • 丟失更新 lost update 后提交的事務把先提交的事務給覆蓋掉

在MySql中設置事務隔離級別

select @@tx_isolation 查詢當前事務隔離幾倍
默認級別是 Repeatable read.


查看事務隔離級別

set session transaction isolation level 設置事務隔離級別
比如:set session transaction isolation level read uncommitted;

在jdbc中設置事務隔離級別

調(diào)用java.sql.Connection中的方法
void setTransactionIsolation(int level);

  • level取值可以看API,設置的有常量

演示

//演示臟讀dirty read,一個事務讀到另一個事務未提交的數(shù)據(jù)

set session transaction isolation level read uncommitted; //設置隔離級別
//在A事務中
start transaction;
update account set money=money-500 where name='aaa';
update account set money=money+500 where name='bbb'; //先不提交事務

//在B事務中
start transaction;
select * from account; //此時,B事務讀取到變化后的數(shù)據(jù),就出現(xiàn)了臟讀
//當A事務提交前,執(zhí)行rollback,commit. B事務再查詢就會發(fā)現(xiàn),數(shù)據(jù)恢復原樣。
//出現(xiàn)兩次查詢結(jié)果不一致問題,出現(xiàn)了不可重復讀。

//解決臟讀問題,將事務的隔離級別設置為read committed;
set session transaction isolation level read committed; //設置隔離級別
//此時解決了臟讀,但還存在不可重復讀,兩次讀取的數(shù)據(jù)不一樣。A事務commit前和commit后

//解決不可重復讀,設置隔離級別為repeatable read;
set session transaction isolation level repeatable read; //設置隔離級別
//當A事務提交后,B事務查詢的與上次提交前的結(jié)果相同。

//Serializable可以解決所有問題
//該設置可以鎖表,A事務未提交前,其它事務無法對此表進行操作。性能較差
轉(zhuǎn)賬匯款操作

代碼

//jsp
<body>
<form action="${ pageContext.request.contextPath }/account" method="POST">
    轉(zhuǎn)入賬戶:<input type="text" name="accountin"><br>
    轉(zhuǎn)出賬戶:<input type="text" name="accountout"><br>
    金額:<input type="text" name="money"><br>
    <input type="submit" name="提交"><br>
</form>
</body>

//ServletAccount.java
public class ServletAccount extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=utf-8");
        
        //得到請求參數(shù)
        String accountIn = request.getParameter("accountin");
        String accountOut = request.getParameter("accountout");
        double money = Double.parseDouble(request.getParameter("money"));
        
        //調(diào)用service
        AccountService service = new AccountService();
        try {
            service.account(accountIn,accountOut,money);
            response.getWriter().write("轉(zhuǎn)賬成功");
        } catch (Exception e) {
            e.printStackTrace();
            response.getWriter().write("轉(zhuǎn)賬失敗");
        }
        
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}


//AccountService.java
public class AccountService {
    //匯款方法
    public void account(String accountIn, String accountOut, double money) throws Exception  {
        //調(diào)用AccountDao中的兩個方法
        AccountDao dao = new AccountDao();
        Connection con = null;
        
        try {
            con = jdbcUtils.getConnection();
            //開啟事務
            con.setAutoCommit(false);
            
            dao.accountIn(con,accountIn,money);
            dao.accountOut(con,accountOut,money);
        } catch (Exception e) {
            e.printStackTrace();
            //出現(xiàn)問題,進行事務回滾
            if(con!=null){
                try {
                    con.rollback();
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
            }
            throw e;
        }finally{
            //事務提交
            if(con!=null){
                try {
                    con.commit();
                    con.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }       
    }
}

//AccountDao.java
public class AccountDao {
    //轉(zhuǎn)入
    public void accountIn(Connection con, String accountIn, double money) throws SQLException, AccountException {
        String sql = "update account set money=money+? where name=?";
        
        PreparedStatement pst = con.prepareStatement(sql);
        pst.setDouble(1, money);
        pst.setString(2, accountIn);
        int row = pst.executeUpdate();
        if(row == 0){
            throw new AccountException("轉(zhuǎn)入失敗");
        }
        
        pst.close();
    }
    
    //轉(zhuǎn)出
    public void accountOut(Connection con, String accountOut, double money) throws SQLException, AccountException {
        String sql = "update account set money=money-? where name=?";
        
        PreparedStatement pst = con.prepareStatement(sql);
        pst.setDouble(1, money);
        pst.setString(2, accountOut);
        int row = pst.executeUpdate();
        if(row == 0){
            throw new AccountException("轉(zhuǎn)出失敗");
        }
        pst.close();
    }
}


//自定義異常 AccountException.java
public class AccountException extends Exception{
    public AccountException() {
        super();    
    }
    public AccountException(String message, Throwable cause) {
        super(message, cause);
    }
    public AccountException(String message) {
        super(message);
    }
    public AccountException(Throwable cause) {
        super(cause);
    }
}

用ThreadLocal解決問題

如何在另一個類中的兩個方法中共享另一個類中的Connection,比如

public interface AccountDao{
//這兩個方法都需要使用同一個Connection對象,需要從調(diào)用方法的地方傳遞,
//但沒有傳參,此時就要用ThreadLocal來解決
  public void accountOut(String accountOut, double money) throws Exception;
  public void accountIn(String accountInt, double monye) throws Exception;
}

丟失更新

丟失更新問題

解決方案詳解:

  • 悲觀鎖(假設丟失更新一定會發(fā)生)
    • 利用數(shù)據(jù)庫內(nèi)部鎖機制,管理事務
    • 允許一張數(shù)據(jù)表添加多個共享鎖,只能添加一個排他鎖
    • 事務在修改記錄過程中,鎖定記錄,別的事務無法并發(fā)修改
    • update 語句默認添加排他鎖
  • 樂觀鎖(假設丟失更新不會發(fā)生)
    • 采用程序中添加版本字段解決丟失更新問題
    • timestamp 時間戳自動更新
create table product(
      id int,
      name varchar(20),
      updatetime timestamp; //時間戳
);
// timastamp 在插入和修改時,都會自動更新當前時間
//如果讀取時版本字段與修改時版本字段不一致,說明別人進行修改過數(shù)據(jù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 很多人喜歡這篇文章,特此同步過來 由淺入深談論spring事務 前言 這篇其實也要歸納到《常識》系列中,但這重點又...
    碼農(nóng)戲碼閱讀 4,923評論 2 59
  • 當一個系統(tǒng)訪問量上來的時候,不只是數(shù)據(jù)庫性能瓶頸問題了,數(shù)據(jù)庫數(shù)據(jù)安全也會浮現(xiàn),這時候合理使用數(shù)據(jù)庫鎖機制就顯得異...
    初來的雨天閱讀 3,694評論 0 22
  • 問題:事務是什么,有什么用? 事務就是一個事情,組成這個事情可能有多個單元,要求這些單元,要么全都成功,要么全都不...
    yeller閱讀 791評論 0 0
  • 當一個系統(tǒng)訪問量上來的時候,不只是數(shù)據(jù)庫性能瓶頸問題了,數(shù)據(jù)庫數(shù)據(jù)安全也會浮現(xiàn),這時候合理使用數(shù)據(jù)庫鎖機制就顯得異...
    JackFrost_fuzhu閱讀 7,956評論 4 83
  • 事務的定義 事務由單獨單元的一個或多個SQL語句組成,在這個單元中,每個MySQL語句是相互依賴的。而整個單獨單元...
    諸葛堅強閱讀 1,193評論 0 3

友情鏈接更多精彩內(nèi)容