1. 程式人生 > >10.2(java學習筆記)JDBC事務簡述

10.2(java學習筆記)JDBC事務簡述

一、事務

  事務是指作為一系列操作組成的一個整體,該整體只有兩種狀態,要麼全部執行,要麼全部不執行。

  當組成這個事務的所有語句都執行成功則該事務執行,只要有一條語句執行失敗則該事務不執行。

  假設這裡有一個insert語句和一個update語句屬於一個事務,從巨集觀上來看,這個事務的狀態只有執行或者不執行。

  從微觀上看,這個事務是由這兩條語句組成的, 每個語句必定有其個體的狀態(成功或者不成功)。

  比如可能是insert成功,update失敗。也可能是insert失敗,update成功。

  當其中只要有一條語句失敗,則傳送回滾(回到該事務執行前的狀態即所有事務都不執行,已成功執行的語句會作廢)。

  該事務不執行。如果所有語句都成功,則該事務執行。

 

  那麼事務是如何劃分的呢,怎麼樣算事務的開始,怎麼樣又算事務的結束呢?

  2.1事務開始

    -連線到資料庫上,並執行了一條DML語句(insert、update、delete)。

    -前一個事務結束後,新輸入的DML語句。

 

  2.2事務結束

    -執行commit,或rollback語句。

    -執行一條DDL語句,如create table,這種情況下回自動執行commit語句。

    -執行一條DCL語句,如grant語句,在這種情況下回自動執行commit語句。

    -斷開與資料庫連線

    -執行一條DML語句出現錯誤時,在這種情況下為這個無效的語句執行rollback。

    DDL、DCL、DML可參閱:https://www.cnblogs.com/kawashibara/p/8961646.html

    假設這裡有五條語句:

      -select

      -insert   ---事務開始---

      -updata

      -delete

      -insert    ---

      commit or DCL or DDL  ---

     結合上面分析,事務從insert開始,如果所有語句都能正常執行,

    那麼事務從insert開始,到commit or DCL or DDL結束,這代表一個事務。

    假如其中只要有一條DML語句執行錯誤,比如第二個insert執行錯誤,

    那麼事務從第一個insert開始,第二個到insert結束。這個事務呈現出一種不執行的狀態。

    即insert updata delete insert都不執行,可以看做回到了第一個insert之前的狀態(回滾rollback)。

 

    這裡需要用到一個函式:

    void rollback();撤消當前事務中所做的所有更改,並釋放此連線物件當前支援的任何資料庫鎖。

    僅當禁用自動提交模式時才應使用此方法。

    但DML語句出現異常時,我們需要呼叫此方法達到回滾(回到該事務執行前的狀態,即該事務沒有執行)。

    

    我們先來看這樣一個例子:

    insert(正常執行) --> insert(正常執行) -->commit();

    事務從insert開始,到commit結束。這整個事務是執行的。(測試前最好把表清空下,避免插入重複的主鍵導致錯誤)

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class TestJDBC{
    public static void main(String[] args){
        final String connectionUrl = "jdbc:mysql://localhost:3306/mybatis";
        String userName = "root";
        String passWord = "123456";
        String insertT = "INSERT INTO `mybatis`.`tadd`"  //正確的insert語句
                + "(`id`, `tname`, `tpwd`, `tstudentnum`) "
                + "VALUES (?, ?, ?, ?);";
        String insertF = "INSERT INTO `mybatis`.`tadd`"  //錯誤的insert語句
                + "(`id`, `tname`, `tpwd`, `tstudentnum`) "
                + "VALUES (?, ?, ?, ?, ?, ?);";
        Connection conn = null;
        PreparedStatement p1 = null;
        PreparedStatement p2 = null;

        try {
            //載入驅動
            Class.forName("com.mysql.cj.jdbc.Driver");
            //建立連線
            conn = DriverManager.getConnection(connectionUrl,userName,passWord);
            conn.setAutoCommit(false);//將自動提交設為false,即不進行自動提交
            
            p1 = conn.prepareStatement(insertT);//第一個insert語句,事務開始(正確的語句)
            setParam(p1,"1","1","1","1");
            p1.execute();
            System.out.println("p1插入完成");
            
            Thread.sleep(5000);//休眠5s
            
            p2 = conn.prepareStatement(insertT);//第二個insert語句  (正確的語句)
            setParam(p2,"2","2","2","2");
            p2.execute();
            System.out.println("p2插入完成");
            conn.commit();//手動提交
            
            System.out.println("執行成功");    
        } catch (SQLException e) {
            try {
                conn.rollback();//事務回滾
            } catch (SQLException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            //依次關閉連線
            try{
                p1.close();
            }catch(Exception e){
                
            }
            try{
                p2.close();
            }catch(Exception e){
            
            }
            try{
                conn.close();
            }catch(Exception e){
            }
        }
    }
    public static void setParam(PreparedStatement p,Object... o){
        int i = 1;
        for(Object temp:o){
            try {
                p.setObject(i, temp);
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            i++;
        }
    }
}
執行結果:
p1插入完成
p2插入完成
執行成功

p1插入成功後會等待5s,5s之後p2進行插入,到最後commit整個事務的狀態是執行的。

我們可以看下資料庫中的資料是插入成功的。

 

    接下來我們去看這樣一個例子:

    insert(正常執行) --> insert(執行錯誤) -->commit();

    事務從insert開始,到錯誤的insert結束,整個事務是不執行的。

    即第一個insert不執行,第二個insert也不執行。

   (由於測試資料依然採用之前的資料,所以測試前需先將表清空)  

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class TestJDBC{
    public static void main(String[] args){
        final String connectionUrl = "jdbc:mysql://localhost:3306/mybatis";
        String userName = "root";
        String passWord = "123456";
        String insertT = "INSERT INTO `mybatis`.`tadd`"
                + "(`id`, `tname`, `tpwd`, `tstudentnum`) "
                + "VALUES (?, ?, ?, ?);";
        String insertF = "INSERT INTO `mybatis`.`tadd`"
                + "(`id`, `tname`, `tpwd`, `tstudentnum`) "
                + "VALUES (?, ?, ?, ?, ?, ?);";
        Connection conn = null;
        PreparedStatement p1 = null;
        PreparedStatement p2 = null;

        try {
            //載入驅動
            Class.forName("com.mysql.cj.jdbc.Driver");
            //建立連線
            conn = DriverManager.getConnection(connectionUrl,userName,passWord);
            conn.setAutoCommit(false);//將自動提交設為false,即不進行自動提交
            
            p1 = conn.prepareStatement(insertT);//正確的insert語句
            setParam(p1,"1","1","1","1");
            p1.execute();
            System.out.println("p1插入完成");
            
            Thread.sleep(5000);
            
            p2 = conn.prepareStatement(insertF);//錯誤insert的語句
            setParam(p2,"2","2","2","2");
            p2.execute();
            System.out.println("p2插入完成");
            conn.commit();//手動提交
            
            System.out.println("執行成功");    
        } catch (SQLException e) {
            try {
                conn.rollback();//事務回滾
            } catch (SQLException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            }
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            //依次關閉連線
            try{
                p1.close();
            }catch(Exception e){
                
            }
            try{
                p2.close();
            }catch(Exception e){
            
            }
            try{
                conn.close();
            }catch(Exception e){
            }
        }
    }
    public static void setParam(PreparedStatement p,Object... o){
        int i = 1;
        for(Object temp:o){
            try {
                p.setObject(i, temp);
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            i++;
        }
    }
}
執行結果;
p1插入完成
java.sql.SQLException: No value specified for parameter 5
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:127)
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:95)
    at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
    at com.mysql.cj.jdbc.ClientPreparedStatement.execute(ClientPreparedStatement.java:414)
    at com.test.jdbc.TestJDBC.main(TestJDBC.java:41)

我們看下資料庫中的資料:

我們會發現,即使第一條插入語句執行了,但由於這個事務中第二條insert發生了錯誤,所以這個事務是不執行的。