Java面向对象系列[v1.0.0][数据库事务处理]

本文详细讲解了SQL事务的构成、ACID特性、事务控制方法,包括自动提交与显式提交/回滚,以及如何在JDBC编程中管理和处理事务。特别关注了MySQL事务、批量更新和中间点操作,确保数据一致性。

SQL语句中的事务控制

事务是由一步或者几步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行,程序和事务时两个不同的概念,通常来说一段程序可以包含多个事务,事务具备4个特性即原子性、一致性、隔离性和持续性,统称为事务的ACID特性

ACID特性

  • 原子性(Atomicity):事务是应用中最小的执行单位,具有不可再分的特征,它是应用中不可再分的最小逻辑执行体
  • 一致性(Consitency):事务执行的结果,必须使数据库从一个一致性状态,变成另一个一致性状态,当数据库只包含事务成功提交的结果时,数据库处于一致性状态,如果系统运行发生中断,某个事务尚未完成而被迫中断,而该未完成的事务对数据库所做的修改已被写入数据库,这样数据库就处于一种不正确的状态或者说是不一致的状态;如果全部执行成功,数据库处于一致性状态,如果执行了一部分而另一部分未成功执行则数据库处于不一致状态,因此一致性是通过原子性来保证的
  • 隔离性(Isolation):各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务都是隔离的,也就是说并发执行的事务之间不能看到对方的中间状态,并发执行的事务之间不能互相影响
  • 持续性(Durability):持续性也成为持久性,说的是事务一旦提交,对数据所做的任何改变都要记录到永久存储器中,通常就是保存到物理数据库中。

数据库事务的组成

  • 一组DML语句,经过这组DML语句修改后的数据将保持较好的一致性
  • 一条DDL语句
  • 一条DCL语句
    DDL和DCL语句最多只能有一条,因为DDL和DCL语句都会导致事务立即提交

提交事务

当事务所包含的全部数据库操作都成功执行后,应该提交(commit)事务,使这些修改永久生效,事务的提交有两中方式

  • 显示提交:使用commit
  • 自动提交:执行DDL或DCL语句,或者程序正常退出

事务回滚

当事物所包含的任意一个数据库操作执行失败后,应该回滚(rollback)事务,使该事务中所做的修改全部失效,事务回滚有两种方式

  • 显示回滚:使用rollback
  • 自动回滚:系统错误或者强行退出

MySQL事务

MySQL默认关闭事务(即打开自动提交),在默认情况下在MySQL控制台输入一条DML语句,这条DML语句将会立即保存到数据库里,MySQL开启对事务的支持,可以通过显示调用如下命令
SET AUTOCOMMIT = {0|1} 0为关闭自动提交,也就开启了对事务的支持

自动提交和开启事务恰好相反,如果开启自动提交就是关闭事务,关闭自动提交就是开启事务

  • 一旦在MySQL的命令行窗口中输入set autocommit=0开启了事务,该命令行窗口里的所有DML语句都不会立即生效,上一个事务结束收第一条DML语句将开始一个新的事务,而后续执行的所有SQL语句都处于该事务中,除非显示使用commit来提交事务,或者正常退出,或者运行DDL或DCL语句导致事务隐式提交
  • 也可以使用rollback回滚来结束事务,使用rollback结束事务将导致本次事务中DML语句所做的修改全部失效

一个MySQL命令行窗口代表一次链接Session,在该窗口里设置set autocommit=0相当于关闭了该链接Session的自动提交,对其他链接不会有任何影响

临时性事务

如果不想关闭整个命令行窗口的自动提交,而只是临时性的开启事务,则可以使用MySQL提供的start transaction或begin两个命令,他们都表示临时性的开启一次事务,处于start transaction或begin后的DML语句不会立即生效,除非使用commit显示提交事务或者执行DDL或DCL语句来隐式的提交事务

begin;
insert into davieyang_test values(null, 'xxx', 1);
insert into davieyang_test values(null, 'yyy', 1);
insert into davieyang_test values(null, 'zzz', 1);
select * from davieyang_test; # 此次查询将看到上边的三条insert结果,但如果新开一个MySQL命令行窗口将看不到这3条insert
rollback; # 回滚事务中的全部修改
select * from davieyang_test; # 再执行查询,那3条insert就看不到了,恢复到begin;之前了

提交:无论是显示提交还是隐式提交都会结束当前事务
回滚:无论是显示回滚还是隐式回滚都会结束当前事务

事务的中间点

MySQL提供了savepoin来设置事务的中间点,通过使用savepoint设置事务的中间点可以让事务回滚到指定中间点,而不是回滚全部事务
savepoint a;
rollback to a;

普通的提交、回滚都会结束当前事务,但回滚到指定中间点因为依然处于事务当中,所以不会结束当前事务

JDBC编程中的事务控制

JDBC链接也提供了事务支持,JDBC链接的事务支持由Connection提供,它默认也打开了自动提交,即关闭事务,如此每条SQL语句一旦执行,便会立即提交到数据库,永久生效无法队医进行回滚操作

可以调用connection的setAutoCommit()方法来关闭自动提交即开启事务
conn.setAutoCommit(false);
还可以调用connection的getAutoCommit()方法来返回该链接的自动提交模式

一旦事务开始之后,程序可以像平常一样创建Statement对象,创建了Statement对象后,可以执行任意多条DML语句

stmt.executeUpdate(...);
stmt.executeUpdate(...);
stmt.executeUpdate(...);
stmt.executeUpdate(...);

这些语句虽然被执行了,但语句所做的修改不会生效,因为事务还没有结束,程序可以调用connection的commit()方法来提交事务
conn.commit();
如果任意一条SQL执行失败,则应该调用connection的rollback()方法来回滚事务
conn.rollback();

实际上,当Connection遇到一个未处理的SQLException异常时候,系统会非正常退出,事务也会自动回滚,但如果程序捕获了该异常,则需要在异常处理块中显示的回滚事务


import java.sql.*;
import java.io.*;
import java.util.*;

public class TransactionTest
{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile) throws Exception
	{
		// 使用Properties类来加载属性文件
		var props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void insertInTransaction(String[] sqls) throws Exception
	{
		// 加载驱动
		Class.forName(driver);
		try (
			Connection conn = DriverManager.getConnection(url, user, pass))
		{
			// 关闭自动提交,开启事务
			conn.setAutoCommit(false);
			try (
				// 使用Connection来创建一个Statment对象
				Statement stmt = conn.createStatement())
			{
				// 循环多次执行SQL语句
				for (var sql : sqls)
				{
					stmt.executeUpdate(sql);
				}
			}
			// 提交事务
			conn.commit();
		}
	}
	public static void main(String[] args) throws Exception
	{
		var tt = new TransactionTest();
		tt.initParam("mysql.ini");
		var sqls = new String[]{
			"insert into student_table values(null, 'aaa', 1)",
			"insert into student_table values(null, 'bbb', 1)",
			"insert into student_table values(null, 'ccc', 1)",
			// 下面这条SQL语句将会违反外键约束,
			// 因为teacher_table中没有ID为5的记录。
			"insert into student_table values(null, 'ccc', 5)" 
		};
		tt.insertInTransaction(sqls);
	}
}

程序中只是开启事务,提交事务,并没有回滚事务的代码,当程序执行到"insert into student_table values(null, 'ccc', 5)"时,会引起外键约束异常,并且未处理该异常,引起程序非正常结束,所以事务自动回滚

CMD

创建cmd文件,由windows执行

set CLASSPATH=%CLASSPATH%;../mysql-connector-java-8.0.13.jar
java TransactionTest
cmd

connection也提供了中间点方法setSavepoint(),设置中间点有两种方式

  • Savepoint setSavepoint(): 在当前事务中创建一个未命名的中间点,并返回代表该中间点的Savepoint对象
  • Savepoint setSavepoint(String name):在当前事务中创建一个具有指定名称的中间点,并返回代表该中间点的Savepoint对象

connection提供了回滚中间点方法rollback(Savepoint savepoint),它是根据中间点对象回滚的并非中间点名称

Java8批量更新

JDBC提供了一个批量更新的功能,使用批量更新时,多条SQL语句将被作为一批操作同时收集提交,但批量更新必须得到底层数据库的支持(可以通过调用DatabaseMetaData的supportsBatchUpdates()方法来查看底层数据库是否支持批量更新)

Statment stmt = conn.createStatement();
stmt.addBatch(sql1);
stmt.addBatch(sql2);
stmt.addBatch(sql3);
stmt.addBatch(sql4);
stmt.addBatch(sql5);
...
stmt.executeLargeBatch();
  • 可以使用executeBatch()方法执行也可以使用executeLargeBatch()方法,区别在于如果批量操作中任何一条SQL语句影响的记录条数可能超过Integer.MAX_VALUE则应该使用executeLargeBatch()
  • 执行executeLargeBatch()将返回一个long[]数组,使用Statement执行DDL、DML语句都将返回一个long值,执行多条DDL、DML语句则返回过个long值,它们组成了这个long[]数组
  • 如果在addBatch()方法中添加了select语句,程序将直接报错

为了让批量操作可以正确的处理错误,必须把批量执行的操作视为单个事务,如果批量更新在执行过程中失败,则让事务回滚到批量操作之前的状态,为此,程序应该在开始批量操作前先关闭自动提交开启事务,然后收集更新语句,当批量操作执行结束后提交事务,并恢复之前的自动提交模式


import java.sql.*;
import java.io.*;
import java.util.*;

public class BatchTest
{
	private String driver;
	private String url;
	private String user;
	private String pass;
	public void initParam(String paramFile) throws Exception
	{
		// 使用Properties类来加载属性文件
		var props = new Properties();
		props.load(new FileInputStream(paramFile));
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		pass = props.getProperty("pass");
	}
	public void insertBatch(String[] sqls) throws Exception
	{
		// 加载驱动
		Class.forName(driver);
		try (
			Connection conn = DriverManager.getConnection(url, user, pass))
		{
			// 关闭自动提交,开启事务
			conn.setAutoCommit(false);
			// 保存当前的自动的提交模式
			boolean autoCommit = conn.getAutoCommit();
			// 关闭自动提交
			conn.setAutoCommit(false);
			try (
				// 使用Connection来创建一个Statement对象
				Statement stmt = conn.createStatement())
			{
				// 循环多次执行SQL语句
				for (var sql : sqls)
				{
					stmt.addBatch(sql);
				}
				// 同时提交所有的SQL语句
				stmt.executeLargeBatch();
				// 提交修改
				conn.commit();
				// 恢复原有的自动提交模式
				conn.setAutoCommit(autoCommit);
			}
			// 提交事务
			conn.commit();
		}
	}
	public static void main(String[] args) throws Exception
	{
		var tt = new TransactionTest();
		tt.initParam("mysql.ini");
		var sqls = new String[]{
			"insert into student_table values(null, 'aaa', 1)",
			"insert into student_table values(null, 'bbb', 1)",
			"insert into student_table values(null, 'ccc', 1)",
		};
		tt.insertInTransaction(sqls);
	}
}

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/select_test?useSSL=false&serverTimezone=UTC
user=root
pass=32147

CMD

创建cmd文件,由windows执行

set CLASSPATH=%CLASSPATH%;../mysql-connector-java-8.0.13.jar
java BatchTest
cmd
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Davieyang.D.Y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值