10.8 事务处理

一、事务的概念和MySQL事务支持

1、概念:事务是由一步或几步数据库操作序列组成的逻辑单元,这系列的操作要么全部执行,要么全部放弃执行。程序和事务是两个不同的概念。一般而言,一段程序包含多个事务。

2、事务的四个特性(简称为ACID性):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持续性(Durability)。

(1)原子性:事务是应用中最小的执行单位,就如原子是自然界最小颗粒,具有不可再分的特征一样,事务是应用中不可再分的最小逻辑执行体。

(2)一致性事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成就被迫中断,而该未完成的事务对数据库所做的修改已被写入数据库。此时,数据库就处于一种不正确的状态。

(3)隔离性:各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务都是隔离的。也就是说,并发执行的事务之间不能看到对方的中间状态,并发执行的事务之间不能互相影响。

(4)持续性:持续性也称为持久性,指事务一旦提交,对数据所做的任何改变都要记录到永久存储器中,通常就是保存进物理数据库。

3、数据库的事务由下列语句组成:
(1)一条DML语句,经过这组DML语句修改后的数据将保持较好的一致性。

(2)一条DDL语句。

(3)一条DCL语句。

DDL和DCL语句最多只有一条,因为DDL和DCL语句都会导致事务立即提交。

4、事务提交方式

当事务所包含的全部数据库操作都执行后,应该提交(commit)事务,这些修改永久生效。

★显式提交:使用commit

★自动提交:执行DDL或DCL语句,或者程序正常退出。

不管是显式提交还是隐式提交,都会结束当前事务。

5、事务回滚

当事务所包含的任意一个数据库操作执行失败后,应该回滚(rollback)事务,使事务中所做的修改全部失效。事务回滚有两种方式:

★显式回滚:使用rollback。

★自动回滚:系统错误或者强行退出。

不管是显式回滚还是隐式回滚,都会结束当前事务。

7、关闭自动提交、开启事务

MySQL默认关闭事务(即打开自动提交),在默认情况下,用户在MySQL控制台输入一条DML语句,这条DML语句将会立即保存到数据库里。为了开启MySQL事务支持,可以显式调用如下命令:

1 SET AUTOCOMMIT={0|1}   0为关闭自动提交,即开启事务

  提示:自动提交和事务开启刚好相反,如果开启自动提交就是关闭事务;关闭自动提交就是开启事务。

  一旦在MySQL命令行窗口输入SET AUTOCOMMIT=0开启事务,该命令行窗口里所有DML语句都不会立即生效,上一个事务结束后第一条DML语句将开始一个新的事务,而后续执行的所有SQL语句都处在该事务中,除非显式使用Commit来提交事务,或者正常退出,或者运行DDL、DCL语句导致事务隐式提交。当然也可以使用rollback回滚来结束事务,使用rollback结束事务将导致本次DML语句所做的修改全部失效。

   一个MySQL命令窗口代表一次连接Session,在该窗口设置SET AUTOCOMMIT=0相当于关闭该连接Session的自动提交,对其他连接不会有影响。

  如果不想关闭整个命令窗口的自动提交,而只是想暂时性地开始一次事务,则可以使用MySQL提供的star transaction或begin两个命令,它们都表示临时性地开始一次事务,处于star transaction或begin后的DML语句不会立即生效,除非使用commit显式提交事务,或者执行DDL、DCL语者来隐式提交事务。

如下SQL语句不会对数据库产生任何影响:

 1 #临时开启事务
 2 begin;
 3 #向student_table1插入三条记录
 4 insert into student_table1
 5 values(null,'xx',1);
 6 insert into student_table1
 7 values(null,'yy',1);
 8 insert into student_table1
 9 values(null,'zz,1);
10 #查询student_table1表的记录
11 select *from student_table1;#①
12 #回滚事务
13 rollback;
14 #再次查询
15 select * from student_table1 ;#②

上面的第①条查询语句将会看到刚刚插入的3条记录,如果打开MySQL的其他命令窗口将看不到这三条记录——这正体现了事务的隔离性。接着程序rollback了事务中的全部修改,执行第②条查询语句将看到数据库又恢复到了事务开始前的状态。

不管是显式提交还是隐式提交,都会结束当前事务;不管是显式回滚还是隐式回滚,都会结束当前事务。

除此之外,MySQL还提供了savepoint来设置事务的中间点,通过使用savepoint设置事务中间点还可以让事务回滚到指定中间点,而不是回滚全部事务。如下SQL语句设置了一个中间点:

1 savapoint a;

回滚到指定中间点:

1 rollback a;

普通的提交、回滚都会结束当前事务,但回滚到指定中间点因为依然处于事务中,所以不会结束当前事务。

二、JDBC事务支持

JDBC也提供了事务支持,JDBC连接的事务支持由Connection提供,Connection默认打开自动提交,即关闭事务,在这种情况下,每条SQL语句一旦执行,并会立即提交到数据库中,永久有效,无法对其进行回滚操作。

1、关闭自动提交,开启事务

可以调用Connection的setAutoCommite()方法来关闭自动提交,开启事务,代码如下:

1 //关闭自动提交,开启事务
2 conn.setAutoCommit(false);

 2、返回连接的自动提交模式

调用Connection提供的getAutoCommit()方法来返回该连接的自动提交模式。

3、提交事务

一旦事务开始以后,程序就可以像平常一样创建Statement对象,创建Statement对象之后,可以执行任意多条DML语句,代码如下:

1 stmt.executeUpdate(...);
2 stmt.executeUpdate(...);
3 stmt.executeUpdate(...);

 上面的SQL语句虽然被执行,但这些语句所做的修改不会生效,因为事务还未结束。如果所有的SQL语句都执行成功,程序可以调用Connection的commit()方法来提交事务,代码如下:

1 //提交事务
2 conn.commit();

4、回滚事务

如果任意一条SQL语句执行失败,则应该使用Connection的rollback()方法来回滚事务。

1 #回滚事务
2 conn.rollback();

下面程序示范了当程序出现了未处理的SQLException异常时,系统将自动回滚事务。实际上,当Connection遇到一个未处理的SQLException异常时,系统会非常正常退出,事务也会自动回滚。但如果程序不活了该异常,则需要在程序中显式回滚事务。

 1 package section7;
 2 
 3 import java.io.FileInputStream;
 4 import java.sql.Connection;
 5 import java.sql.DriverManager;
 6 import java.sql.Statement;
 7 import java.util.Properties;
 8 
 9 public class TransactionTest
10 {
11     private  String  driver;
12     private  String url;
13     private String user;
14     private String pass;
15     public void initParam(String paramFile)
16         throws Exception
17     {
18         //使用Properties类加载属性
19         Properties props=new Properties();
20         props.load(new FileInputStream(paramFile));
21         driver=props.getProperty("driver");
22         user=props.getProperty("user");
23         url=props.getProperty("url");
24         pass=props.getProperty("pass");
25     }
26     public void insertInTransaction(String[] sqls)
27         throws Exception
28     {
29         //加载驱动
30         Class.forName(driver);
31         try(
32                 Connection conn = DriverManager.getConnection(url,user,pass);
33                 )
34         {
35             //关闭自动提交
36             conn.setAutoCommit(false);
37             try(
38                     //使用Connection创建一个Statementd对象
39                     Statement stmt=conn.createStatement()
40                     )
41             {
42                 //循环多次执行SQL语句
43                 for(var sql:sqls)
44                 {
45                     stmt.executeUpdate(sql);
46 
47                 }
48             }
49             //提交事务
50             conn.commit();
51         }
52     }
53     public static void main(String[] args)
54         throws Exception
55     {
56         var tt=new TransactionTest();
57         tt.initParam("src\\mysql.ini");
58         var sqls=new String[]{
59                 "insert into student_table values(null,'aaa',1);",
60                 "insert into student_table values(null,'bbb',1);",
61                 "insert into student_table values(null,'ccc',1);",
62                 //下面这条语句将会违反外键约束
63                 //因为teacher_table表中没有ID为5的记录
64                 "insert into student_table1 values(null,'aaa',5);"//
65         };
66         tt.insertInTransaction(sqls);
67     }
68 }
View Code

上面程序中执行到第4条sql语句时,这条语句将出现外键约束异常,该异常没有得到处理,引起程序的非正常结束,所事务自动回滚。

 Connection也提供了设置中间点的方法:

1、Savepoint setSavePoint():在当前事务中创建一个未命名的中间点,并返回该中间点的Savepoint 对象。

 2、Savepoint setSavePoint(String name):在当前事务中创建一个指定名称的中间点,并返回该中间点的Savepoint 对象。(没有太大必要指定中间点名称)

Connection提供rollback(Savepoint savepoint)方法回滚到指定中间点。

三、使用批量更新

JDBC还提供了一个批量更新功能,使用批量更新时,多条SQL语句将被作为一批操作被同时收集,并同时提交。

提示:批量更新批量必须得到底层数据库的支持,可以通过调用MetabaseMetaData的supportsBatchUpdates()方法来查看底层数据库是否支持批量操作。

批量跟新步骤:

①先创建一个Statement对象,然后利用该对象的addBatch()方法将多条SQL语句收集起来。

②最后调用Java 8为Statement对象新增的executeLargeBatch()(或原有的executeBatch())方法同时执行这些SQL语句。

下面代码示范了如何执行这些批量更新:

1 Statement stmt=conn.createStatement();
2 //使用Statement同时收集多条SQL语句
3 stmt.addBatch(sql1);
4 stmt.addBatch(sql2);
5 stmt.addBatch(sql3);
6 ...
7 //同时执行所有的SQL语句
8 stmt.executeLargeBatch();

 执行executeLargeBatch()方法将返回一个long[]数组,因为使用Statement执行DDL、DML语句都将返回一个long值,而执行多条DDL、DML语句将返回多个long值,多个long值就组成这个long[]数组。如果在批量更新的addBatch()方法中添加了select查询语句,程序将直接出现错误。

为了让批量操作可以正确地处理错误,必须把批量执行的操作视为单个事务,如果批量更新在执行过程中失败,则让事务回滚到批量操作开始之前的状态。为了达到这种效果,程序应该在开始批量操作之前先关闭自动提交,然后开始收集更新语句,当批量操作执行结束后,提交事务,并恢复之间的提交模式。如下代码示范了JDBC的批量更新:

 1 package section7;
 2 
 3 import java.io.FileInputStream;
 4 import java.sql.Connection;
 5 import java.sql.Driver;
 6 import java.sql.DriverManager;
 7 import java.sql.Statement;
 8 import java.util.Properties;
 9 
10 public class BatchTest
11 {
12     private  String  driver;
13     private  String url;
14     private String user;
15     private String pass;
16     public void initParam(String paramFile)
17             throws Exception
18     {
19         //使用Properties类加载属性
20         Properties props=new Properties();
21         props.load(new FileInputStream(paramFile));
22         driver=props.getProperty("driver");
23         user=props.getProperty("user");
24         url=props.getProperty("url");
25         pass=props.getProperty("pass");
26     }
27     public void insertBatch(String[] sqls)
28         throws Exception
29     {
30         //加载驱动
31         Class.forName(driver);
32         try(
33                 Connection conn= DriverManager.getConnection(url,user,pass)
34                 )
35         {
36             //保存当前的自动提交模式
37             boolean autoCommit=conn.getAutoCommit();
38             //关闭自动提交
39             conn.setAutoCommit(false);
40             try(
41                     //使用Connection创建一个Statement对象
42                     Statement stmt=conn.createStatement()
43                     )
44             {
45                 //for循环多次执行SQL语句
46                 for(var sql:sqls)
47                 {
48                     stmt.addBatch(sql);
49                 }
50                 //提交所有的SQL语句
51                 stmt.executeLargeBatch();
52                 //提交修改
53                 conn.commit();
54                 //恢复原有的自动提交模式
55                 conn.setAutoCommit(autoCommit);
56             }
57         }
58     }
59     public static void main(String[] args)
60         throws Exception
61     {
62         var bt=new BatchTest();
63         bt.initParam("src\\mysql.ini");
64         var sqls=new String[]{
65                 "insert into student_table values(null,'aaa',1)",
66                 "insert into student_table values(null,'bbb',1)",
67                 "insert into student_table values(null,'ccc',1)"
68         };
69         bt.insertBatch(sqls);
70     }
71 }
View Code

posted @ 2020-04-16 10:53  小新和风间  阅读(273)  评论(0编辑  收藏  举报