spring 事务 @Transactional

  • 推荐阅读:spring事务详解
    spring 事务 @Transactional_第1张图片

一、事务的ACID特性

  • 原子性 Atomicity:一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚

    主要涉及 InnoDB 事务,相关特性:事务的提交,回滚,信息表

  • 一致性 consistency:数据库总是从一个一致性的状态转换到另一个一致性的状态
    在事务开始前后,数据库的完整性约束没有被破坏。例如违反了唯一性,必须撤销事务,返回初始状态

    主要涉及内部 InnoDB 处理,以保护数据不受崩溃,相关特性:双写缓冲、崩溃恢复

  • 隔离性 isolation:每个读写事务的对象对其他事务的操作对象能相互分离,即:事务提交前对其他事务不可见
    通常内部加锁实现

    主要涉及事务,尤其是事务隔离级别,相关特性:隔离级别、innodb锁的底层实现细节

  • 持久性 durability:一旦事务提交,则其所做的修改会永久保存到数据库

    涉及 MySQL 软件特性与特定硬件配置的相互影响,相关特性:

    • 4个配置项:双写缓冲开关、事务提交刷新log的级别、binlog同步频率、表文件
    • 写缓存、操作系统对于fsync()的支持、备份策略等

二、事务的属性

要保证事务的 ACID 特性,spring 给事务定义了6个属性 @Transactional(key1=*,key2=*...)

  • 事务名称:可手动指定事务名称,对应注解中的属性 value、transactionManager
  • 隔离级别: 为解决数据库容易出现的问题,分级加锁处理策略。 对应注解中的属性 isolation
  • 超时时间: 定义一个事务执行过程多久算超时,以便超时后回滚,对应注解中的属性 timeout
  • 是否只读:表示这个事务只读取数据但不更新数据, 对应注解中的属性 readOnly
  • 传播机制: 对事务的传播特性进行定义,共有7种类型。对应注解中的属性 propagation
  • 回滚机制:定义遇到异常时回滚策略,对应注解中的属性 rollbackFor、noRollbackFor、rollbackForClassName、noRollbackForClassName

1、隔离级别

(1) 现象(三种问题)

  • 脏读(Drity Read):事务 A 更新记录但未提交,事务 B 查询出 A 未提交记录
  • 不可重复读(Non-repeatable read):事务 A 读取一次,此时事务 B 对数据进行了更新或删除操作,事务 A 再次查询数据不一致
  • 幻读(Phantom Read):事务 A 读取一次,此时事务 B 插入一条数据事务 A 再次查询,记录多了

(2) IndoDB事务模型(一致性非锁定读VS锁定读)

MVCC 的读操作可以分为快照读和当前读:

  • 快照读:普通的 select
  • 当前读
    • select * from table where ? lock in share mode; (加S锁)
    • select * from table where ? for update; (加X锁)
      insert, update, delete 操作前会先进行一次当前读(加X锁)

1. 一致性非锁定读(快照读)

一致性非锁定读:指 InnoDB 存储引擎通过多版本控制的方式来读取当前执行时间数据库中行的数据

  • 若读取的行正在执行 DELETE 或 UPDATE 操作,则读取操作不会因此等待行上锁的释放
  • 相反,InnoDB 会去读取行的一个快照数据

spring 事务 @Transactional_第2张图片

2. 锁定读(当前读)

innoDB 对 select 语句支持两种锁定读:

  1. SELECT...FOR UPDATE:对读取的行加排它锁(X锁),其他事务不能对已锁定的行再加任何锁
  2. SELECT...LOCK IN SHARE MODE:对读取的行加共享锁(S锁),其他事务可以再加S锁,X锁会阻塞等待

注:这两种锁都必须处于事务中,事务 commit,锁释放,所以必须 beginstart transaction 开启一个事务或 set autocommit=0 (mysql默认是 1,即执行完 sql 立即提交)

(3) 分级处理策略(四种隔离级别)

  1. Read Uncommitted(读取未提交内容):可能读取其它事务未提交的数据(脏读+不可重复读+幻读)
  2. Read Committed(读取提交内容):一个事务只能看见已经提交事务所做的改变(不可重复读+幻读)
    • select...from:一致性非锁定读的数据快照(MVCC)是最新版本,但其他事务可能会有新的 commit,所以同一 select 可能返回不同结果 —— 不可重复读问题
    • select...from for update:record lock 行级锁
  3. Repeatable Read(可重读):
    • select…from:同一事务内多次一致性非锁定读,取第一次读取时建立的快照版本(MVCC),保证了同一事务内部的可重复读 —— 狭义的幻读问题得到解决(DB 插入了数据,只不过读不到)
    • select...from for update(or LOCK IN SHARE MODE):next-key lock下一键锁
      1)对于具有唯一搜索条件的唯一索引,innoDB 只锁定找到的索引记录(next-key lock 降为 record lock)
      2)对于其他非索引或非唯一索引,InnoDB 会对扫描的索引范围进行锁定,使用 next-key locks,阻塞其他 session 对间隙的insert 操作 —— 彻底解决广义的幻读问题(DB 没插入数据)
  4. Serializable(可串行化):最高的隔离级别,在每个读的数据行上加上共享锁 LOCK IN SHARE MODE

    这个级别可能导致大量的超时现象和锁竞争,主要用于分布式事务

2、传播机制

  1. PROPAGATION_REQUIRED:默认设置,若当前事务不存在,则创建一个新的,通常定义事务同步作用域
  2. PROPAGATION_SUPPORTS:若不存在事务,则以非事务方式执行

    注意:小心使用 PROPAGATION_SUPPORTS

  3. PROPAGATION_MANDATORY:若当前事务不存在,则抛出异常

    注意:PROPAGATION_MANDATORY 范围内的事务同步总是由周围的事务驱动

  4. PROPAGATION_REQUIRES_NEW:创建一个新事务,若存在当前事务,则挂起当前事务

    注意:

    • 实际事务挂起不会在所有事务管理器上开箱即用,适用于 JtaTransactionManager,需要 TransactionManager 支持
    • PROPAGATION_REQUIRES_NEW 范围总是定义自己的事务同步,现有同步将被挂起并适当地恢复
  5. PROPAGATION_NOT_SUPPORTED:不支持当前事务,若存在事务,则挂起当前事务,始终以非事务方式执行

    注意:

    • 实际事务挂起不会在所有事务管理器上开箱即用,适用于 JtaTransactionManager,需要 TransactionManager 支持
    • 事务同步在 PROPAGATION_NOT_SUPPORTED 范围内不可用,现有同步将被挂起并适当地恢复
  6. PROPAGATION_NEVER:不支持当前事务,若当前事务存在,则抛出异常

    注意:事务同步在 PROPAGATION_NEVER 范围内不可用

  7. PROPAGATION_NESTED
    • 若当前事务存在,则在嵌套事务中执行
    • 若当前没有事务,类似 PROPAGATION_REQUIRED(创建一个新的)

    注意:实际创建嵌套事务只对特定的事务管理器有效,开箱即用,只适用于 DataSourceTransactionManager(JDBC 3.0驱动)

spring 事务 @Transactional_第3张图片

三、简单样例

Spring 事务的两种实现方式:

  • 编程式事务管理: 使用底层源码可实现更细粒度的事务控制

    spring 推荐使用 TransactionTemplate 典型的模板模式

  • 申明式事务管理: 添加 @Transactional 注解,并定义传播机制+回滚策略

    基于 Spring AOP 实现,本质是对方法前后进行拦截,然后:

    • 在目标方法开始之前创建或加入一个事务
    • 在执行完目标方法之后根据执行情况提交或回滚事务

1、申明式事务管理

@Transactional(propagation= Propagation.REQUIRED, rollbackFor = Exception.class)
@Override
public void addUserBalanceAndUser(String name, BigDecimal balance) {
     
	log.info("[addUserBalanceAndUser] begin!!!");
	//1.新增用户
    userService.addUser(name);
    //2.新增用户余额
    UserBalance userBalance = new UserBalance();
    userBalance.setName(name);
    userBalance.setBalance(new BigDecimal(1000));
    this.addUserBalance(userBalance);
    log.info("[addUserBalanceAndUser] end!!!");
}

applicationContext.xml 文件配置


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 	
       		http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">


<bean id="appTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"/> 
bean>
<tx:annotation-driven transaction-manager="appTransactionManager"/>

2、编程式事务管理

@Override
public void addUserBalanceAndUserWithinTT(String name, BigDecimal balance) {
     
	//实现一个没有返回值的事务回调
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
     
    	@Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
     
        	try {
     
            	log.info("[addUserBalanceAndUser] begin!!!");
                //1.新增用户
                userService.addUser(name);
                //2.新增用户余额
                UserBalance userBalance = new UserBalance();
                userBalance.setName(name);
                userBalance.setBalance(new BigDecimal(1000));
                userBalanceRepository.insert(userBalance);
                log.info("[addUserBalanceAndUser] end!!!");
                //注意:这里catch住异常后,设置setRollbackOnly,否则事务不会滚。当然如果不需要自行处理异常,就不要catch了
			} catch (Exception e) {
     
            	// 异常回滚
                status.setRollbackOnly();
                log.error("异常回滚!,e={}",e);
            }
        }
    });
}
  1. 推荐:可以不用 try catch,transactionTemplate.execute 会自动捕捉异常并回滚
  2. 特殊情况才使用:若有业务异常需要特殊处理,记得 status.setRollbackOnly(); 标识为回滚

四、源码分析

  • 推荐阅读:spring事务详解(三)源码详解

你可能感兴趣的:(spring,工作学习)