1.事务的概念

事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
例如:A——>B转帐,对应于如下两条sql语句

update from account set money=money+100 where name='B';
update from account set money=money-100 where name='A';

2.MySQL数据库中操作事务命令

2.1.编写测试SQL脚本,如下:

/*创建账户表*/
create table account(
id int primary key auto_increment,
name varchar(40),
money float
);

/*插入测试数据*/
insert into account(name,money) values('A',1000);
insert into account(name,money) values('B',1000);
insert into account(name,money) values('C',1000);

下面我们在MySQL数据库中模拟A——B转帐这个业务场景

2.2.开启事务(start transaction)

Mysql默认是自动提交提交!
使用"start transaction"开启MySQL数据库的事务,如下所示:
JDBC专题(六)-JDBC事务_sql
我们首先在数据库中模拟转账失败的场景,首先执行update语句让A用户的money减少100块钱,如下图所示:
JDBC专题(六)-JDBC事务_数据库_02
然后我们关闭当前操作的dos命令行窗口,这样就导致了刚才执行的update语句的数据库的事务没有被提交,那么我们对A用户的修改就不算是是真正的修改了,下次在查询A用户的money时,依然还是之前的1000,如下图所示:
JDBC专题(六)-JDBC事务_回滚_03

2.3.提交事务(commit)

下面我们在数据库模拟A——B转账成功的场景
JDBC专题(六)-JDBC事务_sql_04
我们手动提交(commit)数据库事务之后,A——B转账100块钱的这个业务操作算是真正成功了,A账户中少了100,B账户中多了100。

2.4回滚事务(rollback)

当开启事务之后,执行相关的SQL操作,但是没有commit(提交),那么MySQL数据库会自动的回滚rollback(撤销), 也可以手动的回滚事务!
JDBC专题(六)-JDBC事务_回滚_05
通过手动回滚事务,让所有的操作都失效,这样数据就会回到最初的初始状态!

3.JDBC中使用事务

当Jdbc程序向数据库获得一个Connection对象时,默认情况下这个Connection对象会自动向数据库提交在它上面发送的SQL语句。若想关闭这种默认提交方式,让多条SQL在一个事务中执行,可使用下列的JDBC控制事务语句

Connection.setAutoCommit(false); //开启事务(start transaction)  关闭自动提交
Connection.rollback();//回滚事务(rollback) 如果出现异常也可以自动回滚
Connection.commit();//提交事务(commit)

3.1.JDBC使用事务范例

在JDBC代码中演示银行转帐案例,使如下转帐操作在同一事务中执行

update account set money=money-100 where name='A'
update account set money=money+100 where name='B'
  • 模拟转账成功时的业务场景
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;

try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://192.168.132.128:3306/employeesdb?useUnicode=true&characterEncoding=utf-8", "root", "123");

conn.setAutoCommit(false);//通知数据库开启事务(start transaction)
String sql1 = "update account set money=money-100 where name='A'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
String sql2 = "update account set money=money+100 where name='B'";
st = conn.prepareStatement(sql2);
st.executeUpdate();
conn.commit();//上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
System.out.println("成功!!!"); //

} catch (Exception e) {
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
  • 模拟转账过程中出现异常导致有一部分SQL执行失败后让数据库自动回滚事务
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;

try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://192.168.132.128:3306/employeesdb?useUnicode=true&characterEncoding=utf-8", "root", "123");

conn.setAutoCommit(false);// 通知数据库开启事务(start transaction)
String sql1 = "update account set money=money-100 where name='A'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
// 用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交,此时数据库会自动执行回滚操作
int x = 1 / 0;
String sql2 = "update account set money=money+100 where name='B'";
st = conn.prepareStatement(sql2);
st.executeUpdate();
conn.commit();// 上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
System.out.println("成功!!!");

} catch (Exception e) {
e.printStackTrace();
}
}
  • 模拟转账过程中出现异常导致有一部分SQL执行失败后让数据库手动回滚事务
public static void main(String[] args) {
Connection conn = null;
PreparedStatement st = null;

try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://192.168.132.128:3306/employeesdb?useUnicode=true&characterEncoding=utf-8", "root", "123");

conn.setAutoCommit(false);// 通知数据库开启事务(start transaction)
String sql1 = "update account set money=money-100 where name='A'";
st = conn.prepareStatement(sql1);
st.executeUpdate();
// 用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交,此时数据库会自动执行回滚操作
int x = 1 / 0;
String sql2 = "update account set money=money+100 where name='B'";
st = conn.prepareStatement(sql2);
st.executeUpdate();
conn.commit();// 上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
System.out.println("成功!!!");

} catch (Exception e) {
try {
//捕获到异常之后手动通知数据库执行回滚事务的操作
conn.rollback();
System.out.println("程序回滚啦");
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}

3.2设置事务回滚点

在开发中,有时候可能需要手动设置事务的回滚点,在JDBC中使用如下的语句设置事务回滚点

Savepoint sp = conn.setSavepoint();
Conn.rollback(sp);
Conn.commit();//回滚后必须通知数据库提交事务

设置事务回滚点范例:

public static void main(String[] args) {

Connection conn = null;
PreparedStatement st = null;
Savepoint sp = null;

try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://192.168.132.128:3306/employeesdb?useUnicode=true&characterEncoding=utf-8", "root", "123");

conn.setAutoCommit(false);// 通知数据库开启事务(start transaction)

String sql1 = "update account set money=money-100 where name='A'";
st = conn.prepareStatement(sql1);
st.executeUpdate();

// 设置事务回滚点
sp = conn.setSavepoint();

String sql2 = "update account set money=money+100 where name='B'";
st = conn.prepareStatement(sql2);
st.executeUpdate();

// 程序执行到这里出现异常,后面的sql3语句执行将会中断
int x = 1 / 0;

String sql3 = "update account set money=money+100 where name='C'";
st = conn.prepareStatement(sql3);
st.executeUpdate();

conn.commit();
} catch (Exception e) {
try {
/**
* 我们在上面向数据库发送了3条update语句,
* sql3语句由于程序出现异常导致无法正常执行,数据库事务而已无法正常提交,
* 由于设置的事务回滚点是在sql1语句正常执行完成之后,sql2语句正常执行之前,
* 那么通知数据库回滚事务时,不会回滚sql1执行的update操作
* 只会回滚到sql2执行的update操作,也就是说,上面的三条update语句中,sql1这条语句的修改操作起作用了
* sql2的修改操作由于事务回滚没有起作用,sql3由于程序异常没有机会执行
*/
conn.rollback(sp);//回滚到设置的事务回滚点
//回滚了要记得通知数据库提交事务
conn.commit();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}