事务是数据库学科中非常重要的机制,它是保证底层数据完整的重要手段,没有事务支持的数据库都是非常脆弱的,本小节将讲解MySQL事务处理的基本技术和以及JDBC的事务支持方法。
18.3.1事务的概念和MySQL事务支持
事务是由一步或几步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行,一般而言,一段程序中可能包含多个事务。事务具备4个特性:原子性(Atomicity)、一致性(Consistency)、 隔离性(Isolation)和持续性(Durability),这4个特性也简称为ACID性。
- 原子性(Atomicity):事务是应用中最小的执行单位,就如原子是自然界的最小颗粒,具有不可再分的特征一样,事务是应用中不可再分的最小逻辑执行体。
- 一致性(Consistency):事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而该未完成的操作对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确的状态。比如银行在两个账户之间转账:从A账户向B账户转入1000元,系统先减少A账户的1000元,然后再为B账户增加1000元。如果全部执行成功,数据库处于一致性状态,如果仅执行完A账户金额的修改,而没有增加B账户的金额,则数据库就处于不一致性状态。因此,一致性是通过原子性来保证的。
- 隔离性(Isolation):各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务都是隔离的。也就是说,并发执行的事务之间不能看到对方的中间状态,并发执行的事务之间不能互相影响。
- 持续性(Durability):持续性也称为持久性(Persistence),指事务一旦提交, 对数据所做的任何改变都要记录到永久存储器中,通常就是保存进物理数据库。
通常情况下,数据库的事务有以下几种可能性:
- 一组DML语句,经过这组DML语句修改后的数据将保持一致性。
- 一条DDL语句。
- 一条DCL语句。
当事务所包含的全部数据库操作都成功执行后,应该提交事务以使这些修改生效。事务提交有显式提交和自动提交两种方式。所有执行DDL或DCL语句都是自动提交的,在MySQL的默认情况下DML语句也是自动提交的,但程序员可以人为的设置DML为显式提交,也就是手动提交方式。
当事务所包含的任意一个数据库操作执行失败后,应该回滚事务,使该事务中所做的修改全部失效。事务回滚有也两种方式:显式回滚和自动回滚。显示回滚是调用rollback命令完成的,而自动回滚是在系统出错或强行退出的情况下完成的。
之前讲过,MySQL在默认情况下对DML语句是自动提交的,如果希望改成显式提交可以调用以下命令完成:
set autocommit=0
如果程序员不想改变默认的事务自动提交方式,但又希望某一次执行语句时能够达到首动听提交的效果,可以使用MySQL提供的start transaction 或begin两个命令,它们都表示临时性地开始一次事务, 处于start transaction或begin后的DML语句不会立即生效,除非使用commit显式提交事务,例如下SQL代码将不会对数据库有任何影响。
begin;
insert into users values ('12307','张三','男','1999-03-02','13800099000');
insert into users values ('12308','李四','男','1999-06-12','13833445566');
insert into users values ('12309','王五','男','1999-09-22','18823455432');
rollback;
之所以上面这段代码不会对数据库有任何影响,就是因为begin命令之后的DML语句已经不再是自动提交,必须调用commit命令显式提交,而以上代码的最末尾调用的是rollback命令完成回滚,这使得数据库又回到了原先的状态。需要注意:无论是提交还是回滚,都会使当前的事务结束。
除此之外,MySQL还提供了savepoint来设置事务的中间点,这个中间点相当于一个标记,程序员可以让事务回滚到指定的中间点,而不是回滚全部事务。下面的SQL语句设置了一个中间点a:
savepoint a;
一旦设置了中间点后,就可以使用rollback回滚到指定中间点,回滚到指定中间点的代码如下:
rollback to a;
需要注意:普通的提交、回滚都会结束当前事务,但回滚到指定中间点因为依然处于事务之中,所以不会结束当前事务。
18.3.2 JDBC的事务支持
JDBC连接也提供了事务支持,JDBC连接的事务支持由Connection提供,Connection 默认打开自动提交模式,在这种情况下,每条SQL语句一旦执行,便会立即提交到数据库,永久生效,无法对其进行回滚操作。程序员可以调用Connection的setAutoCommit()方法来关闭自动提交模式,代码如下:
con.setAutoCommit (false);
相应的,Connection接口的getAutoCommit()方法可以返回该连接的自动提交模式。
一旦事务开始之后,程序可以像平常一样创建Statement对象,创建了Statement 对象之后,可以执行任意多条DML语句,如下代码所示: .
stm.executeUpdate(sql1);
stm.executeUpdate(sql2);
stm.executeUpdate(sql3);
.上面这些SQL语句虽然被执行了,但这些SQL语句所做的修改不会生效,因为事务还没有结束。如果所有的SQL语句都执行成功,程序可以调用Connection的commit()方法来提交事务,代码如下:
con.commit() ;
如果任意一条SQL语句执行失败,则应该用Connection的rollback()方法来回滚事务,代码如下:
con.rollback();
实际上,当程序执行时出现一个未处理的SQLException 异常时,系统将会非正常退出,事务也会自动回滚。但如果程序捕获了该异常,则需要在异常处理块中显式地回滚事务。下面的【例18_07】展示了程序在出现没有捕获的异常时系统自动回滚的效果。
【例18_07 自动回滚事务】
Exam18_07.java
import java.sql.*;
public class Exam18_07 {
public static void main(String[] args) throws Exception{
Connection con = Util1.getConneciton();//用Util1的方法获得Connection对象
con.setAutoCommit(false);//关闭自动提交
Statement stm = con.createStatement();
stm.executeUpdate("insert games value(7,'五子棋','棋牌')");
//下面这条语句无法执行成功并产生异常,因为主键冲突
stm.executeUpdate("insert games value(7,'中国象棋','棋牌')");//①;
con.commit();//提交事务
Util1.close(null,stm,con);//释放资源
}
}
【例18_07】中的Connection对象con被设置为关闭自动提交事务,程序中执行了两条插入数据的SQL语句,但第二条SQL语句因主键冲突不能执行成功,因此会产生异常,而main()方法并未捕获这个异常,这样事务会自动回滚,因而两条SQL语句都不会生效。在执行完本例后,读者查询games表会发现程序中试图插入的两条数据并未出现在数据库中。
Connection也提供了设置中间点的方法,如表18-6所示。
表18-6 Connection设置中间点的方法
方法 | 功能 |
Savepoint setSavepoint() | 在当前事务中创建一个未命名的中间点,并返回代表该中间点的Savepoint对象 |
Savepoint setSavepoint(String name) | 在当前事务中创建一个具有指定名称的中间点,并返回代表该中间点的Savepoint对象。 |
通常来说,设置中间点时没有太大的必要指定名称,因为Connection回滚到指定中间点时并不是根据名字回滚的,而是根据中间点对象回滚的,Connection 提供了rollback(Savepoint savepoint)方法回滚到指定中间点。
18.3.3批量更新
JDBC还提供了一个批量更新的功能,使用批量更新时,多条SQL语句将被作为一批操作被同时被执行。使用批量更新也需要先创建一个Staterment对象,然后利用该对象的addBatch()方法将多条SQL语句同时收集起来,最后调用executeLargeBatch()或executeBatch()方法同时执行这些SQL语句。只要批量操作中任何一条SQL语句影响的记录条数可能超过Integer.MAX_ VALUE,就应该使用executeLargeBatch(方法,而不是executeBatch()方法。下面的代码展示了如何执行批量更新。
Statement stm = con.createStatement() ;
//使用Statement同时收集多条SQL语句
stm.addBatch(sql1) ;
stm.addBatch (sql2) ;
stmt.addBatch(sql3) ;
//同时执行所有的SQL语句
stmt. executeLargeBatch();
执行executeLargeBatch()方法将返回一个long[]数组,因为使用Statement执行DDL、DML语句都将返回一个long值,而执行多条DDL、DML语句将会返回多个long值,多个long值就组成了这个long[]数组。如果在批量更新的addBatch()方法中添加了select查询语句,程序将直接出现错误。
为了让批量操作可以正确地处理错误,必须把批量执行的操作视为单个事务,如果批量更新在执行过程中失败,则让事务回滚到批量操作开始之前的状态。为了达到这种效果,程序应该在开始批量操作之前先关闭自动提交,然后开始收集更新语句,当批量操作执行结束后,提交事务并恢复之前的自动提交模式。下面的【例18_08】展示了如何使用JDBC的批量更新。
【例18_08 批量更新】
Exam18_08.java
import java.sql.*;
public class Exam18_08 {
public static void main(String[] args) {
Connection con = null;
Statement stm = null;
try {
con = Util1.getConneciton();//用Util1的方法获得Connection对象
con.setAutoCommit(false);//关闭自动提交
stm = con.createStatement();
//一组SQL语句
String[] sqls = {
"insert games value(7,'五子棋','棋牌')",
"insert games value(8,'中国象棋','棋牌')",
"insert games value(9,'消消乐','益智')"
};
//用循环的形式把要执行的SQL语句收集起来
for (String sql : sqls) {
stm.addBatch(sql);
}
//同时执行所有的SQL语句
stm.executeLargeBatch();
//提交修改
con.commit();
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
Util1.close(null,stm,con);//释放资源
}catch (SQLException e){
e.printStackTrace();
}
}
}
}
执行完【例18_08】的案例代码后,读者再查询games表会看到表中新增加了3条数据,但再次执行这个程序会引发异常,这是因为产生了主键冲突。
本文字版教程还配有更详细的视频讲解,小伙伴们可以点击这里观看。
标签:语句,事务,JDBC,第十八章,回滚,事务处理,提交,SQL,执行 From: https://blog.51cto.com/mugexuetang/5984799