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发生了错误,所以这个事务是不执行的。

 

posted @ 2019-01-12 12:40  gcmh  阅读(359)  评论(0编辑  收藏  举报