一、事务
事务是指作为一系列操作组成的一个整体,该整体只有两种状态,要么全部执行,要么全部不执行。
当组成这个事务的所有语句都执行成功则该事务执行,只要有一条语句执行失败则该事务不执行。
假设这里有一个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发生了错误,所以这个事务是不执行的。