事务介绍
事务处理在数据库开发中有着非常重要的作用,所谓事务就是所有的操作要么一起成功,要么一起失败。事务本身具有原子性(Atomicity),一致性(Consistency),隔离性或独立性(Isolation),持久性(Durability)4个特性
,这4个特性也被称为ACID特性。
- 原子性:原子性是事务最小的单元,是不可再分割的单元,相当于一个个小小的数据库操作,如果这些操作必须同时完成,如果有一个失败了,则一切的操作将全部失败。例如,A给B转账,如果A转账失败了,则B收账也就失败。
- 一致性:指的是在数据库操作的前后是完全一致的,保证数据的有效性,如果事务正常操作则系统会维持有效性,如果事务出现了错误,则回到最原始的状态,也要维持其有效性,这样保证事务开始时和结束时系统处于一致状态。例如:A给B转账,如果转账成功,则保持其一致性,即A的钱减少x元,B的钱增加x元。如果现在A给B转账失败,则保持操作之前的一致性,即A的钱不会减少,B的钱不会增加
- 隔离性:多个事务可以同时进行而且彼此之间无法访问,只有当事务完成最终操作时才可以看到结果。
- 持久性:当一个系统崩溃是,一个事务依然可以坚持提交,当一个事务完成后,操作的结果保存在磁盘中,永远不会被回滚。例如在转账操作时,所有资金数都保存在磁盘中,所以即使系统发生错误,用户账户里的资金也不会增加或减少。
MySQL对事务的支持
在MySQL中提供了如下表所示的几个命令,可以进行事务的处理。
序号 | 命令 | 描述 |
---|---|---|
1 | set autocommit=0 | 取消自动提交处理,开启事务处理 |
2 | set autocommit=1 | 打开自动提交处理,关闭事务处理 |
3 | start transaction | 启动事务 |
4 | begin | 启动事务相当于执行start transaction |
5 | commit | 提交事务 |
6 | roolback | 回滚全部操作 |
7 | savepoint 事务保存点名称 | 设置事务保存点 |
8 | rollback to savepoint 保存点名称 | 回滚操作保存点 |
以上所有操作都是针对一个session
的,在数据库操作中把每一个连接到此数据库上的用户都称为一个session
。
在MySQL中,如果要应用事务处理,则应该按照以下的顺序输入命令。
- 取消自动提交:执行
set autocommint=0
。这样所有的更新指令并不会立刻发送到数据库的表中,而只存在于当前的session
。 - 开启事务:执行
start transaction
或begin
- 编写数据库更新语句:如增加,修改,删除。可以在编写的更新语句之前记录事务的保存点,使用
savepoint
命令 - 提交事务:如果确信数据库的修改没有任何的错误,则使用
commit
提交事务。在提交之前对数据库所做的全部操作都保存在session
中。 事务回滚:如果发现执行的SQL语句有错误,则使用
rollback
命令全部撤销,或者使用rollback to savepoint
记录点,让其回滚到指定的位置。
当一个事务进行时,其他的session
是无法看到此事务的操作状态的,即此session
对数据库所做的一切修改,如果没有提交事务,则其他session
是无法看到当前session
操作结果的。执行JDBC的事务处理
先来看一种情况,现在要求在数据库中执行5条
SQL
语句,这些SQL
语句本身需要保持一致,即要么同时成功,要么同时失败。
先来看看不使用事务的一个例子:
不使用事务处理,一次性插入5条数据
为了演示方便,这里先把test表中的记录全都删除掉。
可以在Navicat for Mysql(或者其他图形化工具)中选中记录删除,
也可以使用命令删除:
delete * from test;
删除后test表为空表:
实例:使用批处理往数据库中添加5条记录
package my.transaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class TransactionDemo1
{
public static final String driver="com.mysql.jdbc.Driver";
public static final String url="jdbc:mysql://127.0.0.1:3306/usersinfo";
public static final String user="root";
public static final String password="root";
public static void main(String[] args) throws ClassNotFoundException, SQLException, ParseException
{
Connection con;
PreparedStatement pstmt;
String sql="insert into test(id,name,sex,grade,major,birthday) "
+ "values(?,?,?,?,?,?)";
Class.forName(driver);
con=DriverManager.getConnection(url,user,password);
pstmt=con.prepareStatement(sql);
java.util.Date today;
java.sql.Date birthday;
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd");
String toString;
for(int i=0;i<5;i++)
{
if(i==1)
{
//故意再次输入已经存在的学号S1000
pstmt.setString(1, "S1000");//id
pstmt.setString(2, "小明_"+i);//name
pstmt.setString(3, "男");//sex
pstmt.setString(4, "大_"+i);//grade
pstmt.setString(5, "计算机科学与技术");//major
today=new java.util.Date();
//获取今天的日期
today=format.parse(format.format(today));
birthday=new java.sql.Date(today.getTime());
pstmt.setDate(6, birthday);//birthday
//打印编译好的SQL语句
toString=pstmt.toString();
System.out.println("mysql>"+toString.substring(toString.lastIndexOf(":")+1).trim());
//加入批处理等待执行
pstmt.addBatch();
}
else {
pstmt.setString(1, "S100"+i);//id
pstmt.setString(2, "小明_"+i);//name
pstmt.setString(3, "男");//sex
pstmt.setString(4, "大_"+i);//grade
pstmt.setString(5, "计算机科学与技术");//major
today=new java.util.Date();
//获取今天的日期
today=format.parse(format.format(today));
birthday=new java.sql.Date(today.getTime());
pstmt.setDate(6, birthday);//birthday
//加入批处理等待执行
toString=pstmt.toString();
System.out.println("mysql>"+toString.substring(toString.lastIndexOf(":")+1).trim());
pstmt.addBatch();
}
}
//执行批处理
int[] result=pstmt.executeBatch();
System.out.println("成功插入了"+result.length+"条数据");
pstmt.close();
con.close();
}
}
运行结果:
- 控制台输出:
mysql>insert into test(id,name,sex,grade,major,birthday) values('S1000','小明_0','男','大_0','计算机科学与技术','2018-07-10')
mysql>insert into test(id,name,sex,grade,major,birthday) values('S1000','小明_1','男','大_1','计算机科学与技术','2018-07-10')
mysql>insert into test(id,name,sex,grade,major,birthday) values('S1002','小明_2','男','大_2','计算机科学与技术','2018-07-10')
mysql>insert into test(id,name,sex,grade,major,birthday) values('S1003','小明_3','男','大_3','计算机科学与技术','2018-07-10')
mysql>insert into test(id,name,sex,grade,major,birthday) values('S1004','小明_4','男','大_4','计算机科学与技术','2018-07-1')
Exception in thread "main" java.sql.BatchUpdateException: Duplicate entry 'S1000' for key 'PRIMARY'
at......
test表中的数据:
从上面的运行结果中来看,test表中只插入了部分的数据。程序只执行正确的SQL语句,但如果要求这些语句是一个整体,只有所有语句都能正确执行的时候才一起全部插入到test表中,如果有一条语句执行错误,这都不插入到test表中。这时,上面的程序不能达到这样的要求。使用事务处理可以达到上述要求的功能。
进行事务处理的步骤:取消Connection中设置的自动提交方式:
con.setAutoCommit(false);
- 如果批处理操作成功,则执行提交事务:
con.commit();
- 如果操作失败,则肯定会发生异常,在异常处理语句中让事务回滚:
con.rollback();
- 如果需要,可以设置Savepoint:
Savepoint sp=con.setSavepoint();
实例:事务的基本操作步骤
还是和之前一样,为了演示方便,先把test表中的全部记录都删除掉:
delete from test;
代码:
package my.transaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TransactionDemo2
{
public static final String driver="com.mysql.jdbc.Driver";
public static final String url="jdbc:mysql://localhost:3306/usersinfo";
public static final String user="root";
public static final String password="root";
public static void main(String[] args) throws ClassNotFoundException,SQLException, ParseException
{
Connection con;
PreparedStatement pstmt;
String sql="insert into test(id,name,sex,grade,major,birthday) "
+ "values(?,?,?,?,?,?)";
Class.forName(driver);
con=DriverManager.getConnection(url,user,password);
//第一步,取消自动提交
con.setAutoCommit(false);
pstmt=con.prepareStatement(sql);
java.util.Date today;
SimpleDateFormat format=new SimpleDateFormat("yyyy-MM-dd");
java.sql.Date birthday;
String toString;
for(int i=0;i<5;i++)
{
if(i==1)
{
//故意再次输入已经存在的学号S1000
pstmt.setString(1, "S1000");//id
pstmt.setString(2, "小丽_"+i);//name
pstmt.setString(3, "女");//sex
pstmt.setString(4, "大_"+i);//grade
pstmt.setString(5, "软件工程");//major
today=new java.util.Date();
//获取今天的日期
today=format.parse(format.format(today));
birthday=new java.sql.Date(today.getTime());
pstmt.setDate(6, birthday);//birthday
//打印编译好的SQL语句
toString=pstmt.toString();
System.out.println("批处理操作>"+toString.substring(toString.lastIndexOf(":")+1).trim());
//加入批处理等待执行
pstmt.addBatch();
}
else {
pstmt.setString(1, "S100"+i);//id
pstmt.setString(2, "小明_"+i);//name
pstmt.setString(3, "男");//sex
pstmt.setString(4, "大_"+i);//grade
pstmt.setString(5, "计算机科学与技术");//major
today=new java.util.Date();
//获取今天的日期
today=format.parse(format.format(today));
birthday=new java.sql.Date(today.getTime());
pstmt.setDate(6, birthday);//birthday
//加入批处理等待执行
toString=pstmt.toString();
System.out.println("批处理操作>"+toString.substring(toString.lastIndexOf(":")+1).trim());
pstmt.addBatch();
}
}
try
{
//执行批处理操作,如果都成功,则会执行到该条下面的语句
int[] success=pstmt.executeBatch();
System.out.println("操作成功,成功插入了:"+success.length+"条记录");
//如果批处理操作成功(不发生异常),则提交事务
con.commit();
} catch (Exception e)
{
//如果批处理操作不成功,出现了异常
//事务回滚
e.printStackTrace();
System.out.println("操作异常,事务回滚");
con.rollback();
}
pstmt.close();
con.close();
}
}
运行结果:
- 控制台部分输出:
批处理操作>insert into test(id,name,sex,grade,major,birthday) values('S1000','小明_0','男','大_0','计算机科学与技术','2018-07-10')
批处理操作>insert into test(id,name,sex,grade,major,birthday) values('S1000','小丽_1','女','大_1','软件工程','2018-07-10')
批处理操作>insert into test(id,name,sex,grade,major,birthday) values('S1002','小明_2','男','大_2','计算机科学与技术','2018-07-10')
批处理操作>insert into test(id,name,sex,grade,major,birthday) values('S1003','小明_3','男','大_3','计算机科学与技术','2018-07-10')
批处理操作>insert into test(id,name,sex,grade,major,birthday) values('S1004','小明_4','男','大_4','计算机科学与技术','2018-07-10')
java.sql.BatchUpdateException: Duplicate entry 'S1000' for key 'PRIMARY'
at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:2045)
at com.mysql.jdbc.PreparedStatement.executeBatch(PreparedStatement.java:1468)
at my.transaction.TransactionDemo2.main(TransactionDemo2.java:73)
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'S1000' for key 'PRIMARY'
at
......略
操作异常,事务回滚
- test表中的数据:
以上程序中加入了事务的处理操作,这样当更新出错后,数据库会进行回滚,则所有的更新操作全部失效。
在程序中可以加入若干的Savepoint
作为事务的保存点。
实例:回滚到保存点,后面输入的不算数
package my.transaction;
import java.sql.Statement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class TransactionDemo3
{
public static final String driver="com.mysql.jdbc.Driver";
public static final String url="jdbc:mysql://localhost:3306/usersinfo";
public static final String user="root";
public static final String password="root";
public static void main(String[] args) throws ClassNotFoundException,SQLException, ParseException
{
Connection con;
Statement stmt;
//加载数据库驱动
Class.forName(driver);
con=DriverManager.getConnection(url,user,password);
//第一步关闭自动提交
con.setAutoCommit(false);
//创建操作
stmt=con.createStatement();
stmt.executeUpdate("insert into test(id,name,sex,grade,major,birthday) "
+ "values('G1000','小赵','男','大三','计算机科学与技术','2015-6-9')");
stmt.executeUpdate("insert into test(id,name,sex,grade,major,birthday) "
+ "values('G1001','小钱','男','大三','计算机科学与技术','2015-6-9')");
//穿件一个保存点
Savepoint sp=con.setSavepoint();
stmt.executeUpdate("insert into test(id,name,sex,grade,major,birthday) "
+ "values('G1002','小孙','男','大三','计算机科学与技术','2015-6-9')");
stmt.executeUpdate("insert into test(id,name,sex,grade,major,birthday) "
+ "values('G1003','小李','男','大三','计算机科学与技术','2015-6-9')");
try{
//回滚的哦保存点,则后面插入的两行不算数
System.out.println("后面输入的不算数");
con.rollback(sp);
//提交事务
con.commit();
}catch(Exception e)
{
}
stmt.close();
con.close();
}
}
运行结果:
后面输入的不算数
test表中的数据:
可以看到test表中只插入了保存点之前的两条记录。