事务的定义:
事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
事务的特性在这里就不详细介绍了,主要是原子性、一致性、隔离性、持久性(简称ACID)
java.sql.Connection类中关于事务的三个方法:
Connection.setAutoCommit(false); —开启事务
Connection.rollback(); —回滚事务
Connection.commit(); —提交事务
1.首先创建操作对象—账户表
在表中我们先给设定初始值:
张三:初始金额10000元
李四:初始金额1元
-- 账户表的创建
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
accountName VARCHAR(20),
money DOUBLE
);
//--------定义类 以及其私有成员变量---------
public class AccountDao {
private Connection conn;
private PreparedStatement pstmt;
假设张三账户中初始余额为10000元,李四初始余额为1元。
1. 没有使用事务(张三向李四转账1000)
@Test
public void trans1(){
String sql_zs = "update account set money=money-1000 where accountName='张三'";
String sql_ls = "update account set money=money+1000 where accountName='李四'";
try {
//获取连接
conn = JdbcUtil.getConnection();
//------------第一次 张三-1000-----------------------
pstmt = conn.prepareStatement(sql_zs);
pstmt.executeUpdate();
//------------第二次 李四+1000-----------------------
pstmt = conn.prepareStatement(sql_ls);
pstmt.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally{
JdbcUtil.closeAll(conn, pstmt, null);
}
}
执行结果:
初始状态:
成功情况:两条SQL都成功执行,张三金额减少1000,李四金额增加1000
这里搞一点小破坏,模拟第二条sql出错,将第二条sql修改成如下情况:
String sql_ls = "update_ account set money=money+1000 where accountName='李四'";
重新执行,出现失败情况:第二条SQL执行出错,出现了张三的金额减少了1000,而李四的金额没变!! 这是绝不允许发生的情况!
因此接下来我们引入事务
2. 使用事务,当发生异常时全部回滚(张三向李四转账1000)
@Test
public void trans2(){
String sql_zs = "update account set money=money-1000 where accountName='张三'";
String sql_ls = "update account set money=money+1000 where accountName='李四'";
try {
//获取连接
conn = JdbcUtil.getConnection();
conn.setAutoCommit(false); //开启事务,需手动进行提交
//------------第一次 张三-1000-----------------------
pstmt = conn.prepareStatement(sql_zs);
pstmt.executeUpdate();
//------------第二次 李四+1000-----------------------
pstmt = conn.prepareStatement(sql_ls);
pstmt.executeUpdate();
} catch (Exception e) {
try {
conn.rollback(); //发生异常时,进行事务回滚操作
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally{
try {
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
}
JdbcUtil.closeAll(conn, pstmt, null);
}
}
执行结果:
初始状态:
成功的情况和上面例1一样:两条SQL都成功执行,张三金额减少1000,李四金额增加1000
这里主要讲一下失败情况:
由于事务的保护机制,即使发生SQL执行出错的情况,事务也能回滚到事务执行的起点,就相当于什么都没发生一样,因此结果会和初始状况一样:
3.使用事务,当发生异常时,回滚到SavePoint 。(张三向李四第一次转账1000,第二次转账500)
Connection还提供了一个功能,就是在发生异常时,回滚到自定义的SavePoint。
这种情况仅适用于在一个事务中执行多条SQL语句,并且确保SavePoint之前的SQL一定能执行成功。
@Test
public void trans3(){
Savepoint sp = null;
String sql_zs1 = "update account set money=money-1000 where accountName='张三'";
String sql_ls1 = "update account set money=money+1000 where accountName='李四'";
String sql_zs2 = "update account set money=money-500 where accountName='张三'";
String sql_ls2 = "update- account set money=money+500 where accountName='李四'";
try {
//获取连接
conn = JdbcUtil.getConnection();
conn.setAutoCommit(false); //开启事务,需手动进行提交
//------------第一次 张三向李四转1000-----------------------
pstmt = conn.prepareStatement(sql_zs);
pstmt.executeUpdate();
pstmt = conn.prepareStatement(sql_ls);
pstmt.executeUpdate();
sp = conn.setSavepoint(); //创建回滚点(切记,这样写要确保第一次转账肯定能执行成功!!!)
//------------第二次 张三向李四转500-----------------------
pstmt = conn.prepareStatement(sql_zs2);
pstmt.executeUpdate();
pstmt = conn.prepareStatement(sql_ls2);
pstmt.executeUpdate();
} catch (Exception e) {
try {
conn.rollback(sp); //发生异常时,进行事务回滚操作
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally{
try {
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
}
JdbcUtil.closeAll(conn, pstmt, null);
}
}
}
执行结果:
四条SQL都执行成功的情况:
张三向李四第一次转账1000,第二次转账500都执行成功:
张三向李四第一次转账1000成功,第二次转账500失败,由于SavePoint设定在第一次转账之后,第二次转账之前,所以当第二次转账发生异常时,事务机制将回滚到第一次转账完成,第二次转账还没开始的时候: