事务
简介
事务指一组最小逻辑操作单元,里面有多个操作组成。组成事务的每一部分必须要同时提交成功,如果有一个操作失败,整个操作就回滚。
事务ACID特性
1、原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
2、一致性(Consistency)
事务的一致性是指事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。 通俗的说:我和你的钱加起来一共是2000,那么不管我和你之间如何转账,转几次账,事务结束后我们的钱相加起来应该还得是2000,这就是事务的一致性。
3、隔离性(Isolation)
指多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。 通俗的说:多个用户并发访问操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
4、持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
默认情况下,Connection 对象处于自动提交模式下,这意味着它在执行每个语句后都会自动提交更改。
如果禁用了自动提交模式,那么要提交更改就必须显式调用commit 方法,否则无法保存数据库更改。
如果连接处于自动提交模式下,则它的所有 SQL 语句将被执行并作为单个事务提交。否则,它的 SQL 语句将聚集到事务中,直到调用 commit 方法或 rollback 方法为止。默认情况下,新连接处于自动提交模式。
事务的引入
1、数据表准备
CREATE TABLE IF NOT EXISTS bank(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL,
money INT NOT NULL
);
INSERT INTO bank VALUES(NULL, '张三', 1000),(NULL, '李四', 1000);
2、转账
package org.westos.demo3;
import org.westos.util.JDBCUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author lwj
* @date 2020/8/11 15:41
*/
public class MyTest {
public static void main(String[] args) throws SQLException {
//事务:一组SQL语句,要么同时成功,要么同时失败
/*
转账:必须用到事务操作,张三-100,李四+100要么同时成功,要么同时失败,不能张三-100,而李四没有+100
张三-100
异常
李四+100
*/
Connection conn1 = JDBCUtil.getConnection();
Connection conn2 = JDBCUtil.getConnection();
PreparedStatement ps1 = conn1.prepareStatement("update bank set money = money - 100 where id = 1");
PreparedStatement ps2 = conn2.prepareStatement("update bank set money = money + 100 where id = 2");
//事务默认自动提交,两个操作分属两个不同的事务
ps1.executeUpdate();
//模拟异常:ps1事务提交了,但是ps2事务还没提交
int i = 10 / 0;
ps2.executeUpdate();
JDBCUtil.close(ps1, conn1);
JDBCUtil.close(ps2, conn2);
}
}
转账
恢复bank表中数据为1000与1000。
1、正常转账
package org.westos.demo3;
import org.westos.util.JDBCUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author lwj
* @date 2020/8/11 16:01
*/
public class MyTest2 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCUtil.getConnection();
conn.setAutoCommit(false);
//设置手动提交事务
ps = conn.prepareStatement("update bank set money = money - 100 where id = 1");
ps.executeUpdate();
ps = conn.prepareStatement("update bank set money = money + 100 where id = 2");
ps.executeUpdate();
//手动提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
} finally {
try {
JDBCUtil.close(ps, conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
2、异常转账
package org.westos.demo3;
import org.westos.util.JDBCUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author lwj
* @date 2020/8/11 16:01
*/
public class MyTest2 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCUtil.getConnection();
conn.setAutoCommit(false);
//设置手动提交事务
ps = conn.prepareStatement("update bank set money = money - 100 where id = 1");
ps.executeUpdate();
int i = 100 / 0;
ps = conn.prepareStatement("update bank set money = money + 100 where id = 2");
ps.executeUpdate();
//手动提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//一旦遇到异常,则回滚事务,不再执行commit
try {
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
} finally {
try {
JDBCUtil.close(ps, conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
异常转账过后,事务回滚,没有commit操作,也就不会有转账,money字段保持不变。
恢复bank表中数据为1000与1000。
3、事务回滚到回滚点
package org.westos.demo3;
import org.westos.util.JDBCUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
/**
* @author lwj
* @date 2020/8/11 16:01
*/
public class MyTest2 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
Savepoint point = null;
try {
conn = JDBCUtil.getConnection();
conn.setAutoCommit(false);
//设置手动提交事务
//第一次转账
ps = conn.prepareStatement("update bank set money = money - 100 where id = 1");
ps.executeUpdate();
ps = conn.prepareStatement("update bank set money = money + 100 where id = 2");
ps.executeUpdate();
point = conn.setSavepoint();
//设置回滚点
//第二次转账
ps = conn.prepareStatement("update bank set money = money - 100 where id = 1");
ps.executeUpdate();
int i = 100 / 0;
ps = conn.prepareStatement("update bank set money = money + 100 where id = 2");
ps.executeUpdate();
//手动提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
//一旦遇到异常,则回滚事务
try {
conn.rollback(point);
//回滚到指定的回滚点
conn.commit();
//如果此处不提交事务,那么将没有机会提交
} catch (SQLException throwables) {
throwables.printStackTrace();
}
} finally {
try {
JDBCUtil.close(ps, conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
在第一次转账与第二次转账之间设置一个回滚点,当发生异常时,事务回滚到回滚点之前的状态。
而且在rollback之后需要有一次commit操作。
当取消设置回滚点,而发生异常时,默认回滚到初始状态,即1000-1000。