事务处理在数据库开发中有着非常重要的作用,所谓事务就是所有的操作要么一起成功,要么一起失败。事务本身具有原子性(Atomicity),一致性(Consistency),隔离性或独立性(Isolation),持久性(Durability)4个特性
,这4个特性也被称为ACID特性。
在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
操作结果的。
先来看一种情况,现在要求在数据库中执行5条SQL
语句,这些SQL
语句本身需要保持一致,即要么同时成功,要么同时失败。
先来看看不使用事务的一个例子:
为了演示方便,这里先把test表中的记录全都删除掉。
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 sp=con.setSavepoint();
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
......略
操作异常,事务回滚
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();
}
}
运行结果:
后面输入的不算数