十、JDBC--事务(四)

参考文档
https://blog.csdn.net/J080624/article/details/73435480

一、数据库事务

1.1 事务的概念

			事务指一组最小的逻辑操作单元,里面有多个操作组成。组成这组操作的各个单元,要不全部成功,要不全部不成功。
			如果有一个操作失败,整个操作就回滚。
			它会使数据从一种状态变换到另一种状态。
			
			例如:A——B转帐,对应于如下两条sql语句
			  update from account set money=money+100 where name=‘b’;
			  update from account set money=money-100 where name=‘a
	


1.2 事务的ACID特性
	+++ 事务ACID特性
	
				原子性(Atomicity)
					事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 
				
				一致性(Consistency)
					事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
				
				隔离性(Isolation)
					事务操作是相互隔离不受影响的。
				
					多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所
					干扰,并发事务之间数据要相互隔离。

					如果不考虑隔离性,会引发安全问题如下:脏读、不可重复读、幻读(虚读)。
					准确地说,并非要求做到完全无干扰。数据库规定了多种事务
					隔离界别,不同的隔离级别对应不用的干扰成都,隔离级别越
					高,数据一致性越好,但并发行越弱。比如对于A对B进行转
					账,A没把这个交易完成的时候,B是不知道A要给他转钱。
				
				
				持久性(Durability)
					一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中。
					
		事务的特性:
				原子性,是一个最小逻辑操作单元 !
				一致性,事务过程中,数据处于一致状态。
				持久性, 事务一旦提交成功,对数据的更改会反映到数据库中。
				隔离性, 事务与事务之间是隔离的。


	1. 事务操作过程中,会触发MySQL的锁机制。

	2. InnoDB默认是行锁,但是如果在事务操作过程中,没有用到索引,那么系统会自动全表检索数据,自动升级为表锁。
		行锁:只有当前行被锁住,别的用户不能操作。
		表锁:整个表被锁住,别的用户不能操作。
		
1.3 mysql的锁机制

如果同时在两个窗口更新同一条数据呢?一个开启手动事务,一个使用默认事务:
在这里插入图片描述
在这里插入图片描述
那么不好意思,后面那个将会抛出错误:

在这里插入图片描述

二、数据库事务-事务操作

	事务操作分为两种:自动事务(默认的),手动事务。
3.1 手动事务
3.1.1 手动事务
	
	Start transaction; --告诉系统内以下所有操作(写)不要直接写入数据库,先存放到事务日志。
	UPDATE account SET money=money-1000 WHERE NAME='小白';
	UPDATE account SET money=money+1000 WHERE NAME='黑四';
	COMMIT;
	
	sql执行完毕,两个sql语句都执行成功。

>>>>>> 开启事务,但未提交,回滚
	
	Start transaction; --告诉系统内以下所有操作(写)不要直接写入数据库,先存放到事务日志。
	UPDATE account SET money=money-1000 WHERE NAME='小白';
	UPDATE account SET money=money+1000 WHERE NAME='黑四';
	
	sql执行完毕,两个sql语句都执行成功。
	虽然都执行成功,但是由于开启了事务,但没有提交,所以事务回滚

>>>>>> 开启事务,提交,出现异常回滚
	
	Start transaction; --告诉系统内以下所有操作(写)不要直接写入数据库,先存放到事务日志。
	UPDATE account SET money=money-1000 WHERE NAME='小白';
	异常...
	UPDATE account SET money=money+1000 WHERE NAME='黑四';
	COMMIT;
	
	sql执行完毕,出现异常回滚。
	
3.1.2 手动事务,设置回滚点
	savepoint 回滚点名字;
	rollback to 回滚点名字;
--开启事务
START TRANSACTION;

update p_user set age = 10 where id = 1;
--设置回滚点
SAVEPOINT point_name;

-- 假设错误操作
update p_user set age = 11 where id = 1;

--回滚到回滚点
ROLLBACK to point_name;

--关闭事务
COMMIT;

SELECT * from p_user where id = 1;
3.2 自动事务
	在MySQL中,默认的是自动事务处理,用户操作完直接同步到数据表。

	自动事务:
		  1. 系统通过autocommit变量进行控制。mysql默认自动事务为开启状态。	
		  2. 如果手动开启了事务,那么将使用手动事务(不再使用默认的自动事务)。
		  3. 如果关闭自动事务,那么需要手动提交(commit)或回滚(rollback)。


3.2.1 查看数据库自动事务状态

在这里插入图片描述

3.2.2 打开或关闭自动事务状态

	关闭自动事务语法:
			set autocommit = 0/off;
	
	开启自动事务语法:
			set autocommit = 1/on;
>>>>>> 手动开启了手动事务,那么将使用手动事务(不在使用自动事务)(默认自动事务为开启状态)
START  TRANSACTION;

update p_user set age= 20 where id = 1;

select * from p_user where id =1 ;

COMMIT;
>>>>>> 如果关闭自动事务,那么需要手动提交(commit)或回滚(rollback)
update p_user set age  = 10 where id = 1;
commit;

三、数据库事务-使用sql设置事务

创建事务,并提交

	START TRANSACTION;
	UPDATE account SET money=money-1000 WHERE NAME='小白';
	UPDATE account SET money=money+1000 WHERE NAME='黑四';
	COMMIT;
	
	sql执行完毕,两个sql语句都执行成功。

创建事务,并回滚

	
	START TRANSACTION;
	UPDATE account SET money=money-1000 WHERE NAME='小白';
	UPDATE account SET money=money+1000 WHERE NAME='黑四';
	ROLLBACK;
	
	sql执行完毕,两个sql都被回滚了,所以结果相当于没有执行。
	

创建事务,创建回滚点,回滚部分。

	
	START TRANSACTION;
	UPDATE account SET money=money-1000 WHERE NAME='小白';
	
	SAVEPOINT sp;  
	UPDATE account SET money=money+1000 WHERE NAME='黑四';
	ROLLBACK TO sp;
	
	sql执行完毕,由于设置了回滚点sp。
	所以第一个sql执行成功,第二个sql回滚。

四、数据库事务-事务原理

	事务开启之后,所有的操作都会临时保存到事务日志里面。
	事务日志只有在得到commit命令才会同步到数据表,其他任何情况都会清空(rollback,断电,断开连接等等)。

在这里插入图片描述

五、JDBC事务处理(JDBC关于事务的API)

	当一个连接对象被创建时,默认情况下是自动提交事务。
	每次执行一个 SQL 语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
	
	为了让多个 SQL 语句作为一个事务执行:
		1.调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务;
		2.在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务;
		3.在出现异常时,调用 rollback(); 方法回滚事务;
		4. 若此时 Connection 没有被关闭, 则需要恢复其自动提交状态。
		5. 

|-- Connection
		void setAutoCommit(boolean autoCommit) ;  设置事务是否自动提交
										          如果设置为false,表示手动提交事务。
		void commit() ;						      手动提交事务
		void rollback() ;						  回滚(出现异常时候,所有已经执行成功的代码需要回退到事务开始前的状态。)
		Savepoint setSavepoint(String name)       设置回滚点

注意:	
		1.mysql默认开启隐式的事务,对已经执行成功的sql,默认提交。

例子:模拟一次转账,不使用事务。

	第一个sql执行成功(不回滚),
	第二个sql执行失败(回滚)。
	
	mysql默认开启隐式性事务,即sql执行完毕,默认提交。
	第一个sql执行完毕,默认提交,所以不会滚。
	第二个sql执行失败,所以回滚。
		@Test
		public void test1() throws SQLException {
			
			Connection conn=null;
			PreparedStatement st1=null;
			PreparedStatement st2=null;
			
			//模拟转账
			String sql1_zh="update account set money=money-1000 where name='小白'";
			String sql1_dz="update account set money=money+1000 where name='黑四'";

			try {
				
				conn = JdbcUtil.getConn();
				
			
				//第一次执行sql  小白转给小黑  转账
				st1 = conn.prepareStatement(sql1_zh);
				int row1 = st1.executeUpdate();
				
				if(1==1){
					throw new RuntimeException();
				}
				
				//第二次执行sql  小白转给小黑  到账
				st2 = conn.prepareStatement(sql1_dz);
				int row2 = st2.executeUpdate();

			} catch (Exception e) {
			
				throw new RuntimeException(e);
			}finally {
							
				JdbcUtil.close(conn, st1);
				JdbcUtil.close(conn, st2);	
			}
		}
		
	

例子:模拟一次转账,使用事务。

	第一个sql执行成功(回滚),
	第二个sql执行失败(回滚)。
	
	由于在执行第一个sql前,已经手动开启事务,
	事务中发生异常,则全部回滚,
	所以第一个sql回滚,第二个sql也回滚。
		
		@Test
		public void test12() throws SQLException {
			
			Connection conn=null;
			PreparedStatement st1=null;
			PreparedStatement st2=null;
			
			//模拟转账
			String sql1_zh="update account set money=money-1000 where name='小白'";
			String sql1_dz="update account set money=money+1000 where name='黑四'";

			try {
				
				conn = JdbcUtil.getConn(); //默认开启隐式事务。
				
				//一、手动开启事务
				conn.setAutoCommit(false);
				
			
				//第一次执行sql  小白转给小黑  转账
				st1 = conn.prepareStatement(sql1_zh);
				int row1 = st1.executeUpdate();
				
				if(1==1){
					throw new RuntimeException();
				}
				
				//第二次执行sql  小白转给小黑  到账
				st2 = conn.prepareStatement(sql1_dz);
				int row2 = st2.executeUpdate();

			} catch (Exception e) {
				
				//二、若发生异常,手动回滚
				conn.rollback();
				
				throw new RuntimeException(e);
			}finally {
				
				//三、手动提交事务
				conn.commit();
				
				JdbcUtil.close(conn, st1);
				JdbcUtil.close(conn, st2);	
			}
		}
		

例子:模拟两次转账,使用事务, 回滚到指定的代码段

	第一次转账执行成功,(不回滚)
	第二次转账执行失败。(回滚)
	
	由于在执行第一次转账执行成功后设置了回滚点,
	事务中发生异常后,又手动回滚到了回滚点,
	所以第一次转账提交,
	第二次转账失败。
		@Test
		public void test12() throws SQLException {
			
			Connection conn=null;
			PreparedStatement st1=null;
			PreparedStatement st2=null;
			PreparedStatement st3=null;
			PreparedStatement st4=null;

			Savepoint sp=null;
			
			//模拟第一次转账
			String sql1_zh="update account set money=money-1000 where name='小白'";
			String sql1_dz="update account set money=money+1000 where name='黑四'";
			
			
			//模拟第二次转账
			String sql2_zh="update account set money=money-1000 where name='小白'";
			String sql2_dz="update account set money=money+1000 where name='黑四'";

			
			try {
				
				conn = JdbcUtil.getConn(); //默认开启隐式事务。
				
				//一、手动开启事务
				conn.setAutoCommit(false);
				
			
				//第一次转账
				st1 = conn.prepareStatement(sql1_zh);
				st1.executeUpdate();
				st2 = conn.prepareStatement(sql1_dz);
				st2.executeUpdate();
				
				//四、设置回滚点
				 sp = conn.setSavepoint();
				
				if(1==1){
					throw new RuntimeException();
				}
				
				//第二次转账
				st3 = conn.prepareStatement(sql2_zh);
				st3.executeUpdate();
				st4 = conn.prepareStatement(sql2_dz);
				st4.executeUpdate();

			} catch (Exception e) {
				
				//二、若发生异常,手动回滚,回滚到某个点
				conn.rollback(sp);
				
				throw new RuntimeException(e);
			}finally {
				
				//三、手动提交事务
				conn.commit();
				
				JdbcUtil.close(conn, st1);
				JdbcUtil.close(conn, st2);	
			}
		}
		
	

五、事务的隔离级别(事务的隔离性)


	对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采
	取必要的隔离机制, 就会导致各种并发问题:

	脏读: 对于两个事物 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的。

	不可重复读: 对于两个事物 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了。

	幻读: 对于两个事物 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行。

数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。

一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。



		多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以
		保证各个线程在获取数据时的准确性。
		

在这里插入图片描述

5.1 如果不考虑隔离性,可能会引发如下问题
5.1.1 脏读
	脏读:
		一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。
		
		这是非常危险的,假设A向B转帐100元,对应sql语句如下所示
		1.update account set money=money-100 while name=‘a’;	
		2.update account set money=money+100 while name=‘b’;
		
		当第1条sql执行完,第2条还没执行(A未提交时),如果此时B查询自己的
		帐户,就会发现自己多了100元钱。如果A等B走后再回滚,B就会损失100元。
5.1.2 不可重复读
	不可重复读:
	
		在一个事务内读取表中的同一数据,多次读取结果不同。
		
		在同一事务中,多次读取同一数据返回的结果有所不同,读到了之前的数据
		后又读到了其他事务已经提交的更新的数据。
		
		例如:
			在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。
			与此同时,
			事务B把张三的工资改为8000,并提交了事务。
			随后,
			在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。



	很多人认为这种情况就对了,无须困惑,当然是后面的为准。我们可以考虑这样一种
	情况,比如银行程序需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个
	事务中针对输出的目的地,进行的两次查询不一致,导致文件和屏幕中的结果不一
	致,银行工作人员就不知道以哪个为准了。

	和脏读的区别是:脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了
	前一事务已提交的数据。
5.1.3 虚读(幻读)
	虚读(幻读)
		
		是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
		
		如丙存款100元未提交,这时银行做报表统计account表中所有用户的总额
	为500元,然后丙提交了,这时银行再统计发现帐户为600元了,造成虚读同样
	会使银行不知所措,到底以哪个为准。
	
5.1.4 第一类丢失更新

A事务撤销时,把已经提交的B事务的更新数据覆盖了。例如:

在这里插入图片描述
这时候取款事务A撤销事务,余额恢复为1000,这就丢失了更新。

5.1.5 第二类丢失更新

A事务覆盖B事务已经提交的数据,造成B事务所做的操作丢失

在这里插入图片描述

5.1.6 总结 – 区别
	脏读:是指在一个事务中读取到另外一个事务还未提交的数据。
	
	不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同,读到了
				之前的数据后又读到了其他事务已经提交的更新的数据。
	幻读:是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
	
	+++ 脏读和不可重复读的区别:
			脏读是读取前一事务未提交的脏数据,
			不可重复读是重新读取了前一事务已提交的数据。
	
	+++ 不可重复读和幻读的区别:
			不可重复度是指读的时候 别的事务修改了数据update
			幻读是指读的时候 别的事务插入或者删除了数据 insert delete 
			
			不可重复读的重点是修改: 
					同样的条件,你读取过的数据,再次读取出来发现值不一样了。
					关注的是数据的变化。
					
			幻读的重点在于新增或者删除: 
					同样的条件,第 1 次和第 2 次读出来的记录数不一样
					关注的是数据条数的变化。
5.2 事务隔离性的设置语句
	数据库共定义了四种隔离级别:
			Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
			
			Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)
			
			Read committed:可避免脏读情况发生(读已提交)。
			
			Read uncommitted:最低级别,以上情况均无法保证。(读未提交)
	

	Oracle 支持的 2 种事务隔离级别:READ COMMITED(默认采用), SERIALIZABLE。
	Oracle 默认的事务隔离级别为: READ COMMITED 。
	
	MySQL 支持 4 中事务隔离级别。
	MySQL 默认的事务隔离级别为: REPEATABLE READ。

在这里插入图片描述

5.2.1 使用sql设置事务隔离级别
	查看当前会话隔离级别
			select @@tx_isolation	

	查看系统当前隔离级别
			select @@global.tx_isolation;

	设置当前 mySQL 连接的隔离级别:
			set  transaction isolation level read committed;

	设置数据库系统的全局的隔离级别:
		 	set  global transaction isolation level read committed;

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
sqlite-jdbc-3.36是一个用于Java编程语言的JDBC驱动程序,用于连接和操作SQLite数据库。SQLite是一种轻量级的嵌入式数据库引擎,没有独立的服务器进程,它将整个数据库作为一个文件存储在主机文件系统中。 sqlite-jdbc-3.36提供了用于连接SQLite数据库的API和工具,使得开发者可以使用Java语言轻松地在应用程序中操作SQLite数据库。它提供了各种功能,如连接数据库、创建和执行SQL语句、事务管理、批处理操作等。 使用sqlite-jdbc-3.36,可以通过以下步骤在Java应用程序中连接和操作SQLite数据库: 1. 下载并导入sqlite-jdbc-3.36的JAR文件到项目中。 2. 加载驱动程序类,这样可以将其注册到Java的JDBC驱动管理器中。 3. 使用JDBC连接字符串指定要连接的SQLite数据库文件路径,并使用驱动程序的getConnection()方法获得一个连接对象。 4. 通过连接对象创建一个语句对象,并使用该对象执行SQL查询或更新语句。 5. 处理和检索结果,可以使用语句对象的executeQuery()方法执行查询SQL语句,并使用结果集对象获取查询结果。 6. 关闭连接和释放资源,最后要确保关闭连接对象和释放相关资源,以防止资源泄漏。 sqlite-jdbc-3.36具有良好的性能和稳定性,可以轻松地与Java应用程序集成,并提供了强大的SQLite数据库操作功能。无论是开发桌面应用程序、移动应用程序还是服务器端应用程序,sqlite-jdbc-3.36都是一个很好的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值