zoukankan      html  css  js  c++  java
  • Spring 中事务控制的 API 介绍

    事务:一个包含多个步骤的业务操作。如果这个包含多个步骤的业务操作被事务管理,则这多个步骤要么同时成功(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

    此接口提供的是事务具体的运行状态,方法介绍如下图:
     
  • 相关阅读:
    CSharpThinkingC# 要点(附加三)
    CSharpThinkingC#3 革新(附加二)
    CSharpThinking委托相关(二)
    C++之this指针与另一种“多态”
    《C++应用程序性能优化::第二章C++语言特性的性能分析》学习和理解
    《C++应用程序性能优化::第一章C++对象模型》学习和理解
    回答总结:C实现“动态绑定”
    编译器对临时变量的优化简单理解
    虚函数表里边保存的不一定是虚函数的地址
    C++对象内存布局测试总结
  • 原文地址:https://www.cnblogs.com/zwh0910/p/14630768.html
Copyright © 2011-2022 走看看