首页 > 编程语言 > 详细

Spring 中事务控制的 API 介绍

时间:2021-04-10 22:19:08      阅读:39      评论:0      收藏:0      [点我收藏+]

事务:一个包含多个步骤的业务操作。如果这个包含多个步骤的业务操作被事务管理,则这多个步骤要么同时成功(commit),要么同时失败(rollback)。

操作:
1. 开启事务:start transaction
2. 提交事务:commit
3. 回滚事务:rollback

银行转账业务存在问题的演示:

1、新建db1数据库,创建表格并插入数据

CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
balance DOUBLE
);
-- 添加数据
INSERT INTO account (NAME, balance) VALUES (Jack, 1000), (Rose, 1000);

技术分享图片

 2、正常情况下,执行以下语句,发现转账正常

-- jack减500
UPDATE account SET balance = balance -500 WHERE NAME = jack;
-- rose加500
UPDATE account SET balance = balance +500 WHERE NAME = rose;

结果为:

技术分享图片

3、如果在执行第一条SQL语句之后出现了异常,以下SQL模拟异常

-- jack减500
UPDATE account SET balance = balance -500 WHERE NAME = jack;
rose加500
UPDATE account SET balance = balance +500 WHERE NAME = rose;

即第一条SQL执行成功,第二条SQL由于异常没有执行

结果为:

技术分享图片

使用事务管理来解决这个问题

转账之前开启事务,当使用了start transaction表示手动提交

-- 开启事务
START TRANSACTION;
-- jack减500
UPDATE account SET balance = balance -500 WHERE NAME = jack;
rose加500
UPDATE account SET balance = balance +500 WHERE NAME = rose;

执行以上SQL,并没有提交,也没有回滚,现在这个事务还没有结束。此时在新的sqlyog窗口中查询,数据没有任何变化,如下图所示

技术分享图片

 但是在本sqlyog窗口中查询发现数据变化了,如下图所示,但是这些数据的变化时临时的变化,并不是持久的变化。当窗口关闭再打开,临时变化会被取消掉。

技术分享图片

 5、出了问题则回滚事务。这样数据库表中的数据没有发生变化,保证了账户的安全性

-- 开启事务
START TRANSACTION;
-- jack减500
UPDATE account SET balance = balance -500 WHERE NAME = jack;
rose加500
UPDATE account SET balance = balance +500 WHERE NAME = rose;
-- 回滚事务
ROLLBACK;

6、发现执行没有问题,则提交事务

-- 开启事务
START TRANSACTION;
-- jack减500
UPDATE account SET balance = balance -500 WHERE NAME = jack;
rose加500
UPDATE account SET balance = balance +500 WHERE NAME = rose;
-- 提交事务
COMMIT;

commit之后,数据发生了持久性的变化,事务随着commit的执行结束了

Mysql数据库中事务默认自动提交(Oracle数据库默认是手动提交的)一条DML语句(增删改)会自动提交一次事务,例如执行以下SQL

UPDATE account SET balance = 1000

执行之后会默认提交一次事务,数据会被持久化更新,当执行了start transaction,就表示手动提交。

如果你开启了事务,没有提交事务,则事务默认会自动回滚。

手动提交需要start transaction,手动提交使用commit。DML语句不开启事务就会自动提交。

事务提交的两种方式:

1、自动提交:mysql就是自动提交的

2、手动提交:需要先开启事务,再提交

修改事务的默认提交方式:

1、先查看事务默认提交方式:1代表自动提交,0代表手动提交

SELECT @@autocommit;

结果如下:

技术分享图片

 2、修改默认提交方式

SET @@autocommit = 0;

如果此时执行DML语句而没有commit,sq语句是不会生效的,即还没有持久化保存。 

Oracle数据库默认是手动提交的,将来用Oracle数据库,执行了增删改操作之后必须commit才会生效,如果没有commit,关闭窗口就会还原到之前的状态。

事务四个特性 ACID:

1、原子性:原子是不可分割的最小单位,要么同时陈宫,要么同时失败。
2、一致性:事务操作前后,数据总量不变。
3、隔离性:多个事务之间相互独立,我们希望多个事务相互独立,不影响相互的操作。真实的情况下,多个事务之间会产生相互影响。要解决这些影响带来的问题,就要了解事务的隔离级别。
4、持久性:事务一旦提交或者回滚,将会持久的更新数据库表

事务的隔离级别:

概念:多个事务之间是隔离的,相互独立,但是如果多个事务操作同一批数据,则会引发一些问题。设置不同的隔离级别就可以解决这些问题。

引发的问题:

1、脏读:一个事务读到另一个事务没有提交的数据。这个问题非常严重。
2、不可重复读:同一个事务中(事务结束之前)两次读取到的数据不一样。
3、幻读:一个事务操作(DML)数据表中所有的记录,比如给所有的记录加100块钱,另一个事务添加了一条数据,则第一个事务查询不到自己的修改。好像产生了幻觉一样。
mysql数据库中看不到幻读的情况,脏读和不可重复读是可以演示出来的。  

四种隔离级别:

1、read uncommitted:读未提交,
  会引发的问题:脏读、不可重复读、幻读。
2、read committed:读已提交,即只有提交的数据才能读到。(Oracle默认)
  会引发的问题:不可重复读、幻读。
3、repeatable read:可重复读(Mysql默认)
  会引发的问题:幻读。
4、serializable:串行化
  可以解决所有问题
 
一般情况下,不会去修改数据库默认的隔离级别,只有特殊的情况下才会修改隔离级别来解决不同的问题。
是不是将隔离级别设置成serializable就行了呢?
注意:隔离级别从read uncommitted到serializable,安全性越来越高,但是效率越来越低,所以要设置合适的隔离级别,既保证安全,有保证效率。
数据库查询隔离级别:
SELECT @@tx_isolation;

结果:

技术分享图片

 数据库设置隔离级别:

SET GLOBAL TRANSACTION ISOLATION LEVEL 级别字符串;

如:

SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

接下来演示脏读和不可重复读,通过设置不同的隔离级别来解决这些问题。

演示读未提交

 1、打开一个cmd窗口,输入msyql -uroot -p123456,use db3;,select * from account;结果如下:

技术分享图片

2、 设置隔离级别为读未提交,这样才能演示脏读的问题。

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

3、开启事务

start transaction;

4、再打开一个窗口,登录mysql,进入db3数据库,查询所有记录,给新窗口开启事务,这样,两个窗口都开启了事务。

5、执行转账操作

-- jack减500
UPDATE account SET balance = balance -500 WHERE NAME = ‘jack‘;
-- rose加500
UPDATE account SET balance = balance +500 WHERE NAME = ‘rose‘;

注意:并没有提交

6、新窗口查询所有记录

技术分享图片

 发现能够查询到旧窗口没有提交的数据,脏读的情况发生,隔离级别设置生效。

7、旧窗口执行rollback,回滚,数据会还原到转账之前的操作。此时新窗口再次查询所有记录,发现转账并没有成功,

技术分享图片

 此时也发生了不可重复读,在同一个事务中,两次读取的数据不一样。

脏读值一个事务还没有提交,另一个事务就能读取到没有提交的数据。

演示读已提交

 设置隔离级别为读已提交,来解决脏读的问题

 1、打开一个cmd窗口,输入msyql -uroot -p123456,use db3;,select * from account;结果如下:

技术分享图片

1、将旧窗口事务的隔离级别设置为读已提交

SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

3、关闭窗口,重新打开,开启事务

start transaction;

4、再打开一个窗口,登录mysql,进入db3数据库,查询所有记录,给新窗口开启事务,这样,两个窗口都开启了事务。

5、在旧窗口中执行转账的操作,

-- jack减500
UPDATE account SET balance = balance -500 WHERE NAME = jack;
-- rose加500
UPDATE account SET balance = balance +500 WHERE NAME = rose;

由于现在的隔离级别是读已提交,且还没有提交

6、此时在新窗口中查询所有记录,发现钱还没有到,只有当旧窗口的事务提交之后,真正的数据发生修改

技术分享图片

 7、旧窗口提交

commit;

8、新窗口查询所有记录,发现转账成功

技术分享图片

 但是还有一个问题,就是在同一个事务中,两次的查询结果不一致,即不可重复读。

 技术分享图片

 有些需求要求在同一个事务中(事务没结束之前),每次读取的数据一模一样。我们希望在同一个事务中,每次查询的数据都是一样的。只有当这个事务结束之后,才会看到其他事务对这个表数据的修改情况。要完成这个需求,我们需要将事务的隔离级别设置成repeatable read

不可重复读指一个事务在提交前和提交后,另一个事务在没有结束之前读取到的数据不一样。

演示可重复度

 设置隔离级别为可重复读,来解决不可重复读的问题

可重复读指事务结束之前可重复读。

1、打开一个cmd窗口,输入msyql -uroot -p123456,use db3;,select * from account;结果如下:

技术分享图片

2、将旧窗口事务的隔离级别设置为读已提交

SET GLOBAL TRANSACTION ISOLATION LEVEL repeatable read;

3、关闭窗口,重新打开,因为设置了隔离级别之后,重新打开才会生效。此时事务的隔离级别为repeatable read,开启事务

start transaction;

4、再打开一个窗口,登录mysql,进入db3数据库,查询所有记录,给新窗口开启事务,这样,两个窗口都开启了事务。

5、在旧窗口中执行转账的操作,

-- jack减500
UPDATE account SET balance = balance -500 WHERE NAME = ‘jack‘;
-- rose加500
UPDATE account SET balance = balance +500 WHERE NAME = ‘rose‘;

由于现在的隔离级别是可重复读,还没有提交

6、在新窗口中查询所有记录,发现数据没有产生任何变化。

技术分享图片

 此时,右边的事务还没有提交,

7、提交旧窗口的事务,此时新窗口还是没有提交事务,新窗口再次查询所有记录,发现数据没有发生变化。说明,可重复读就生效了。

技术分享图片

 8、当把新窗口中的事务提交或回滚了之后,再次查询,才可以看到数据表的变化,

可重复读是指一个事务提交前和提交后,另一个事务在没有结束之前可重复读。

演示串行化

 serializable:串行化就是一个锁表的动作,一个事务在操作数据表,另一个事务是不可以操作数据表的,

1、打开一个cmd窗口,输入msyql -uroot -p123456,use db3;,select * from account;结果如下:

技术分享图片

2、将旧窗口事务的隔离级别设置为读已提交

SET GLOBAL TRANSACTION ISOLATION LEVEL serializable;

3、关闭窗口,重新打开,因为设置了隔离级别之后,重新打开才会生效。此时事务的隔离级别为serializable,开启事务

start transaction;

4、再打开一个窗口,登录mysql,进入db3数据库,查询所有记录,给新窗口开启事务,这样,两个窗口都开启了事务。

5、在旧窗口中执行转账的操作,

-- jack减500
UPDATE account SET balance = balance -500 WHERE NAME = ‘jack‘;
-- rose加500
UPDATE account SET balance = balance +500 WHERE NAME = ‘rose‘;

由于现在的隔离级别是串行化,还没有提交

6、在新窗口中查询所有记录,发现光标一直在闪,查询的动作并没有执行,只有当旧窗口事务提交或回滚之后,新窗口才能完成查询的动作,相当于这张表被锁住了。。

7、提交旧窗口的事务,新窗口立即回查询出来数据

串行化是将表锁住了,即一个事务在操作数据,其他事务无法操作相同数据。

 

使用Connection对象来管理事务

* 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
  在执行sql之前开启事务
* 提交事务:commit()
  当所有sql都执行完提交事务
* 回滚事务:rollback()
  在catch中回滚事务

PlatformTransactionManager接口

我们自己写了一个事务管理器,spring提供了事务管理器,我们拿过来直接用就可以。

此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法,如下图:
技术分享图片
我们在开发中都是使用它的实现类,如下图:

真正管理事务的对象

org.springframework.jdbc.datasource.DataSourceTransactionManager:使用 SpringJDBC 或 iBatis 进行持久化数据时使用
org.springframework.orm.hibernate5.HibernateTransactionManager:使用Hibernate 版本进行持久化数据时使用

TransactionDefinition 

它是事务的定义信息对象,里面有如下方法:
技术分享图片

事务的传播行为指什么情况下必须有事务(增删改必须有事务Required),什么情况下可有可没有(查询可有可没有事务supports)

只读:增删改read-only="false"

读写:只有查询方法才能用只读,read-only="true"

事务的隔离级别

技术分享图片

使用数据库默认的隔离级别,mysql数据库的默认隔离几级别为可重复读,Oracle数据库的默认隔离级别为读已提交。

事务的传播行为

REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:以非事务方式运行,如果当前存在事务,抛出异常
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。

超时时间

默认值是-1,没有超时限制。如果有,以秒为单位进行设置。

是否是只读事务

建议查询时设置为只读,增删改为读写型。

TransactionStatus

此接口提供的是事务具体的运行状态,方法介绍如下图:
技术分享图片

 

 

Spring 中事务控制的 API 介绍

原文:https://www.cnblogs.com/zwh0910/p/14630768.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!