美文网首页
Spring事务行为

Spring事务行为

作者: zhanghTK | 来源:发表于2018-07-01 18:07 被阅读0次
    title: Spring事务行为
    date: 2017-12-08 21:15:53
    tags:
      - Java
      - Spring
    categories: Spring
    
    

    事务

    特性

    原子性:事务是一个不可分割的单位,事务中的操作要么都发生,要么都不发生。
    一致性:事务执行前后数据的完整性必须保持一致
    隔离性:多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离
    持久性:一个事务一旦被提交,它对数据库中的数据的改变就是永久性的,即使数据库发生故障也不应该对其有任何影响

    隔离级别

    隔离级别引发的问题:

    • 脏读:一个事务读取到了另一个事务改写但还未提交的数据(如果这些数据被回滚,则读到的数据是无效的)
    • 不可重复读:一个事务执行两次相同的查询,中间另一个事务更新了数据,前后两次读到的数据内容会不一致
    • 幻读:一个事务中执行两次相同的查询,中间另一个事务增删了数据,前后两次查询出的数据(规模)行数不一致

    不可重复读 & 幻读区别:

    • 不可重复读强调数据内容的一致性,幻读强调数据内容、规模的一致性
    • 如果以悲观锁解决不可重复读和幻读:
      • 解决不可重复读只需要在事务中对数据行加锁,避免其他事务修改即可
      • 解决幻读需要在事务中加表锁,避免其他事务增加、删除数据

    隔离级别:

    隔离级别 含义 脏读 不可重复读 幻读 备注
    READ_UNCOMMITED 允许读还未提交的改变了的数据
    READ_COMMITED 允许在并发事务提交后读取 Oracle 默认
    REREATABLE_READ 对相同字段的多次读取是一致的,除非数据被本事务改变 MySQL默认
    SERIALIZABLE 完全服从 ACID 的隔离级别

    MySQL 与 幻读

    MySQL InnoDB 在默认事务(REREATABLE_READ)下可解决不可重复读,不保证避免幻读,可以使用间隙锁(next-key locks)来解决幻读。可以参考以下示例 MySQL的InnoDB的幻读问题

    表结构 & 事务级别:

    mysql> show create table t_bitfly\G;
    CREATE TABLE `t_bitfly` (
    `id` bigint(20) NOT NULL default '0',
    `value` varchar(32) default NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=gbk
    mysql> select @@global.tx_isolation, @@tx_isolation;
    +-----------------------+-----------------+
    | @@global.tx_isolation | @@tx_isolation  |
    +-----------------------+-----------------+
    | REPEATABLE-READ       | REPEATABLE-READ |
    +-----------------------+-----------------+
    

    幻读场景一:

    t Session A                   Session B
    |
    | START TRANSACTION;          START TRANSACTION;
    |
    | SELECT * FROM t_bitfly;
    | empty set
    |                             INSERT INTO t_bitfly
    |                             VALUES (1, 'a');
    |
    | SELECT * FROM t_bitfly;
    | empty set
    |                             COMMIT;
    |
    | SELECT * FROM t_bitfly;
    | empty set
    |
    | INSERT INTO t_bitfly VALUES (1, 'a');
    | ERROR 1062 (23000):
    | Duplicate entry '1' for key 1
    v (shit, 刚刚明明告诉我没有这条记录的)
    

    幻读场景二:

    t Session A                  Session B
    |
    | START TRANSACTION;         START TRANSACTION;
    |
    | SELECT * FROM t_bitfly;
    | +------+-------+
    | | id   | value |
    | +------+-------+
    | |    1 | a     |
    | +------+-------+
    |                            INSERT INTO t_bitfly
    |                            VALUES (2, 'b');
    |
    | SELECT * FROM t_bitfly;
    | +------+-------+
    | | id   | value |
    | +------+-------+
    | |    1 | a     |
    | +------+-------+
    |                            COMMIT;
    |
    | SELECT * FROM t_bitfly;
    | +------+-------+
    | | id   | value |
    | +------+-------+
    | |    1 | a     |
    | +------+-------+
    |
    | UPDATE t_bitfly SET value='z';
    | Rows matched: 2  Changed: 2  Warnings: 0
    | (怎么多出来一行)
    |
    | SELECT * FROM t_bitfly;
    | +------+-------+
    | | id   | value |
    | +------+-------+
    | |    1 | z     |
    | |    2 | z     |
    | +------+-------+
    |
    v
    

    间隙锁解锁幻读

    t Session A                 Session B
    |
    | START TRANSACTION;        START TRANSACTION;
    |
    | SELECT * FROM t_bitfly
    | WHERE id<=1
    | FOR UPDATE;
    | +------+-------+
    | | id   | value |
    | +------+-------+
    | |    1 | a     |
    | +------+-------+
    |                           INSERT INTO t_bitfly
    |                           VALUES (2, 'b');
    |                           Query OK, 1 row affected
    |
    | SELECT * FROM t_bitfly;
    | +------+-------+
    | | id   | value |
    | +------+-------+
    | |    1 | a     |
    | +------+-------+
    |                           INSERT INTO t_bitfly
    |                           VALUES (0, '0');
    |                           (waiting for lock ...
    |                           then timeout)
    |                           ERROR 1205 (HY000):
    |                           Lock wait timeout exceeded;
    |                           try restarting transaction
    |
    | SELECT * FROM t_bitfly;
    | +------+-------+
    | | id   | value |
    | +------+-------+
    | |    1 | a     |
    | +------+-------+
    |                           COMMIT;
    |
    | SELECT * FROM t_bitfly;
    | +------+-------+
    | | id   | value |
    | +------+-------+
    | |    1 | a     |
    | +------+-------+
    v
    

    共享锁与排他锁

    t Session A                      Session B
    |
    | START TRANSACTION;             START TRANSACTION;
    |
    | SELECT * FROM t_bitfly;
    | +----+-------+
    | | id | value |
    | +----+-------+
    | |  1 | a     |
    | +----+-------+
    |                                INSERT INTO t_bitfly
    |                                VALUES (2, 'b');
    |                                COMMIT;
    |                (Session B 提交)
    |
    | SELECT * FROM t_bitfly;
    | (普通查询)
    | +----+-------+
    | | id | value |
    | +----+-------+
    | |  1 | a     |
    | +----+-------+
    |
    | SELECT * FROM t_bitfly LOCK IN SHARE MODE;
    | (加共享锁查询:事务对数据行加共享锁后,可进行读写操作;
    |  其他事务可对该数据加共享锁,但不能加排他锁,且只能读数据,不能修改数据)
    | +----+-------+
    | | id | value |
    | +----+-------+
    | |  1 | a     |
    | |  2 | b     |
    | +----+-------+
    |
    | SELECT * FROM t_bitfly FOR UPDATE;
    | (排他锁查询:事务对数据加上排他锁后,其他事务不能对该数据加任何的锁。
       获取排他锁的事务既能读取数据,也能修改数据)
    | +----+-------+
    | | id | value |
    | +----+-------+
    | |  1 | a     |
    | |  2 | b     |
    | +----+-------+
    |
    | SELECT * FROM t_bitfly;
    | +----+-------+
    | | id | value |
    | +----+-------+
    | |  1 | a     |
    | +----+-------+
    v
    

    Spring 与事务

    接口

    Spring 事务管理高层抽象主要的接口有:

    • 事务管理器:PlatformTranscationManager
    • 事务定义信息:TransactionDefinition
    • 事务具体运行状态:TransactionStatus

    使用 TransactionDefinition 定义事务信息,由 PlatformTransactionManager 负责执行事务,执行的结果记录在 TransactionStatus。

    PlatformTransactionManager

    包含多个实现,可以为不同持久化框架提供不同实现

    实现 说明
    DataSourceTransactionManager 使用 Spring JDBC 或 MyBatis 进行持久化数据时使用
    HibernateTransactionManager 使用 Hibernate 进行持久化数据时使用
    JpaTransactionManager 使用 JPA 进行持久化时使用
    JdoTransactionManager Jdo 持久化机制时使用
    JtaTransactionManager 使用 JTA 管理事务

    TransactionDefinition

    • 常量

      ISOLATION_XXX 定义了事务的隔离级别

      PROPAGATION_XXX 定义了事务的传播行为

      TIMEOUT_DEFAULT 默认超时

    • 方法

      获得事务以上信息

    隔离级别

    Spring 事务隔离级别所有事务隔离级别,默认使用 DB 的事务隔离级别

    传播行为

    传播行为解决事务方法相互调用时,事务的处理方式。Spring 事务提供的传播行为:

    事务传播行为 含义
    PROPAGATION_REQUIRED 支持当前事务,如果不存在就新建一个
    PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
    PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常
    PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
    PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
    PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
    PROPAGATION_NESTED 如果当前事务存在,则嵌套事务(保存点)
    PROPAGATION_REQUIRED
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
      methodB();
    }
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodB() {
    }
    
    methodB();  // 当前上下文不存在事务,methodB 开启一个新的事务
    methodA();  // 当前上下文不存在事务,methodA 开启一个新的事务,当执行内部 methodB() 时,methodB 发现当前上下文有事务,因此就加入到当前事务中来
    
    PROPAGATION_SUPPORTS
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
      methodB();
    }
    
    @Transactional(propagation = Propagation.SUPPORTS)
    public void methodB() {
    }
    
    methodB();  // methodB 以非事务的方式执行
    mtthodA();  // 当前上下文不存在事务,methodA 开启一个新的事务,当执行内部 methodB() 时,methodB 加入 methodA 的事务
    
    PROPAGATION_MANDATORY
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
    doSomeThingA();
      methodB();
    }
    
    
    // 事务属性为REQUIRES_NEW
    @Transactional(propagation = Propagation.MANDATOR)
    public void methodB() {
    }
    
    methodB();  // 当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);
    methodA();  // 当前上下文不存在事务,methodA 开启一个新的事务,当执行内部 methodB() 时,methodB 加入 methodA 的事务
    
    PROPAGATION_REQUIRES_NEW
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
      doSomeThing1();
      methodB();
      doSomeThing2();
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
    }
    

    当调用 methodB() ,当前上下文不存在事务,methodB 开启一个新的事务

    当调用 methodA() 相当于执行了以下的代码:

    
    TransactionManager tm = null;
    try{
      // 获得一个JTA事务管理器
      tm = getTransactionManager();
      tm.begin();// 开启一个新的事务
      Transaction ts1 = tm.getTransaction();
      doSomeThing1();
      tm.suspend1();// 挂起当前事务
      try{
        tm.begin();// 重新开启第二个事务
        Transaction ts2 = tm.getTransaction();
        methodB();
        ts2.commit();// 提交第二个事务
      } Catch(RunTimeException ex) {
        ts2.rollback();// 回滚第二个事务
      } finally {
        // 释放资源
      }
      // methodB执行完后,恢复第一个事务
      tm.resume(ts1);
      doSomeThing2();
      ts1.commit();// 提交第一个事务
    } catch(RunTimeException ex) {
      ts1.rollback();// 回滚第一个事务
    } finally {
      // 释放资源
    }
    

    上面的 ts1 和 ts2 是两个独立的事务,互不干扰, ts2 是否成功并不依赖于 ts1。如果 methodA 在调用 methodB 方法后的 doSomeThing2 发生异常,methodB 并不受影响结构依然没提交,但 methodA 的其他代码则会被回滚。

    使用 PROPAGATION_REQUIRES_NEW 需要使用 JtaTransactionManager 作为事务管理器。

    PROPAGATION_NOT_SUPPORTED
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
      doSomeThing1();
      methodB();
      doSomeThing2();
    }
    
    @Transactional(propagation = Propagation.PROPAGATION_NOT_SUPPORTED)
    public void methodB() {
    }
    

    总是以非事务的形式执行,当 methodA 执行到 methodB(); 时先挂起事务,再执行 methodB(), 完成后再恢复 methodA 的事务继续执行 doSomeThing2 方法。

    使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。

    PROPAGATION_NEVER

    总是非事务地执行,如果存在一个活动事务,则抛出异常。

    PROPAGATION_NESTED
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA(){
      doSomeThing1();
      methodB();
      doSomeThing2();
    }
    
    @Transactional(propagation = Propagation.NEWSTED)
    public void methodB(){
    }
    

    当调用 methodB() ,则按照 PROPAGATION_REQUIRED 执行,当前上下文不存在事务,methodB 开启一个新的事务

    当调用 methodA() 相当于执行了以下的代码:

    Connection con = null;
    Savepoint savepoint = null;
    try{
      con = getConnection();
      con.setAutoCommit(false);
      doSomeThing1();
      savepoint = con2.setSavepoint();
      try{
        methodB();
      } catch(RuntimeException ex) {
        con.rollback(savepoint);
      } finally {
        //释放资源
      }
      doSomeThing2();
      con.commit();
    } catch(RuntimeException ex) {
      con.rollback();
    } finally {
      //释放资源
    }
    

    在调用 methodB 之前,先调用 setSavepoint 方法,保存当前的状态到 savepoint。如果 methodB 执行失败,则恢复到 savepoint 保存的状态。

    如果外层事务执行失败,会回滚内层事务所做的动作;

    如果内层事务执行失败,不会引起外层事务的回滚;

    使用JDBC 3.0驱动时,仅仅支持 DataSourceTransactionManager 作为事务管理器,PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)

    TransactionStatus

    维护,获取事务的各种状态

    使用 Spring 事务

    Spring 提供编程式和声明式的事务管理,具体的代码实现可以参考:spring-tx-test

    相关文章

      网友评论

          本文标题:Spring事务行为

          本文链接:https://www.haomeiwen.com/subject/xtjseftx.html