事务是逻辑上的一组操作,或者说一个独立的工作单元。事务内的语句,要么全部执行成功,要么全部执行失败。
数据一旦提交,就不可回滚。数据意味着提交的情况:
connection.setAutoCommit(false);
对 DDL 无效。connection.setAutoCommit(false);
对 DML 取消自动提交。JDBC 中让多条 SQL 语句作为一个事务执行:
connection.setAutoCommit(false);
,取消自动提交事务;connection.commit();
,提交事务;connection.rollback();
,回滚事务。注意:若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 connection.setAutoCommit(true);
。尤其是在使用数据库连接池技术时,执行 close()
方法前,建议恢复自动提交状态。
package cn.parzulpan.jdbc.ch06;
import cn.parzulpan.jdbc.util.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @Author : parzulpan
* @Time : 2020-12-01
* @Desc : 事务问题,AA 用户 向 BB 用户转账 100
*/
public class TransactionTest {
// 出现事务问题
@Test
public void test1() {
String sql1 = "update user_table set balance = balance - 100 where user = ?";
JDBCUtils.update(sql1, "AA");
// 模拟网络异常
System.out.println(10 / 0);
String sql2 = "update user_table set balance = balance + 100 where user = ?";
JDBCUtils.update(sql2, "BB");
}
// 考虑事务问题
@Test
public void test2() {
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
// 取消事务自动提交
connection.setAutoCommit(false);
String sql1 = "update user_table set balance = balance - 100 where user = ?";
JDBCUtils.update(connection, sql1, "AA");
// 模拟网络异常
System.out.println(10 / 0);
String sql2 = "update user_table set balance = balance + 100 where user = ?";
JDBCUtils.update(connection, sql2, "BB");
// 若没有异常,则提交事务
connection.commit();
} catch (Exception e) {
e.printStackTrace();
try {
// 如有异常,则回滚事务
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
} finally {
try {
// 恢复每次 DML 操作的自动提交功能,针对于数据库连接池时
connection.setAutoCommit(true);
} catch (Exception e) {
e.printStackTrace();
}
// 关闭连接
JDBCUtils.closeResource(connection, null, null);
}
}
}
事务的 ACID 属性:
数据库的并发问题:
隔离级别越高,数据的一致性就越好,但并发行就越弱。
四种事务隔离级别:
TRANSACTION_READ_UNCOMMITTED
读未提交:允许事务读取未被其他事务提交的变更,出现 脏读、不可重复读、幻读等问题。TRANSACTION_READ_COMMITTED
读已提交:只允许事务读取已经被其他事务提交的变更,避免 脏读问题,出现 不可重复读、幻读等问题。这是 Oracle 默认的事务隔离级别TRANSACTION_REPEATABLE_READ
可重复读:确保在同一个事务中多次读取同样记录的结果是一致的,避免 脏读、不可重复读问题,出现 幻读等问题。这是 MySQL 默认的事务隔离级别。TRANSACTION_SERIALIZABLE
串行化:确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表进行增删改操作,避免上面所有问题。设置事务隔离级别:
每个数据库连接都有一个全局变量 @@tx_isolation
,表示当前的事务隔离级别。
查看当前的事务隔离级别:
select @@tx_isolation;
设置当前数据库连接的隔离级别:
set transaction isolation level read committed;
设置数据库全局的隔离级别:
set global transaction isolation level read committed;
创建 MySQL 用户,并授予权限:
create user parzulpan identified by ‘xxx‘;
# 授予网络方式登陆的用户,对所有库所有表的全部权限,并设置密码
grant all privileges on *.* to parzulpan@‘%‘ identified by ‘xxx‘;
# 授予本地方式登陆的用户,对 test 库所有表的增删改查权限,并设置密码
grant select, insert, delete, update on test.* to parzulpan@localhost identified by ‘xxx‘;
package cn.parzulpan.jdbc.ch06;
import cn.parzulpan.jdbc.bean.UserTable;
import cn.parzulpan.jdbc.util.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
/**
* @Author : parzulpan
* @Time : 2020-12-01
* @Desc : 事务隔离级别
*/
public class TransactionTest1 {
// 模拟 事务隔离级别
@Test
public void test1() throws Exception{
Connection connection = JDBCUtils.getConnection();
// TRANSACTION_READ_UNCOMMITTED 读未提交数据
// 允许事务读取未被其他事务提交的变更,出现 脏读、不可重复读、幻读等问题
// 先运行 test2(),然后运行 test1(),发现数据已更改,但是10秒后,再然后运行 test1(),发现数据已恢复
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
// TRANSACTION_READ_COMMITTED 读已提交数据
// 只允许事务读取已经被其他事务提交的变更,避免 脏读问题,出现 不可重复读、幻读等问题
// 先运行 test2(),然后运行 test1(),发现数据未更改,10秒后,再然后运行 test1(),发现数据依然未更改
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
// TRANSACTION_REPEATABLE_READ 可重复读
// 确保在同一个事务中多次读取同样记录的结果是一致的,避免 脏读、不可重复读问题,出现 幻读等问题
// 打开 test2() 自动事务提交,先运行 test2(),然后运行 test1(),发现数据未更改,10秒后,再然后运行 test1(),发现数据依然未更改
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
System.out.println(connection.getTransactionIsolation());
connection.setAutoCommit(false);
String sql = "select user, password, balance from user_table where user = ?";
JDBCUtils jdbcUtils = new JDBCUtils();
UserTable userTable = jdbcUtils.getQuery(connection, UserTable.class, sql, "CC");
System.out.println(userTable);
}
// 模拟 事务隔离级别
@Test
public void test2() throws Exception{
Connection connection = JDBCUtils.getConnection();
System.out.println(connection.getTransactionIsolation());
connection.setAutoCommit(false);
String sql = "update user_table set balance = ? where user = ?";
JDBCUtils jdbcUtils = new JDBCUtils();
jdbcUtils.update(connection, sql, 5000, "CC");
Thread.sleep(5000);
System.out.println("修改结束"); // 注意此时事务并未提交
}
}
原文:https://www.cnblogs.com/parzulpan/p/14129976.html