一,什么是事务,怎么理解事务?
事务是保证数据库数据一致性的核心,原生JDBC事务代码冗余,侵入性强。Spring基于AOP动态代理进行封装,提供声明式,编程式两种事务实现方式。
二,事务特性,Spring实现事务的底层原理
原子性:事务内所有操作,要么全部成功提交,要么全部失败回滚,不可以部分执行
一致性:事务执行前后,数据库数据完整性,约束规则不被破坏
隔离性:多个并发事务相互隔离,互不干扰,通过隔离级别控制隔离程度
持久性:事务提交成功后,服务器宕机,重启不丢失
三,Spring事务的两种实现方式
3.1 声明式事务(主流)
需要添加依赖,如下:
特点:基于AOP动态代理,无代码侵入,使用简单。通过@Transactional注解实现。
生效范围:仅作用于public方法
方法执行前开启事务,方法执行结束没有异常就提交事务,否则回滚事务,最后结束事务。
3.2 编程式事务(特殊场景)
特点:需要手动控制事务的开启,提交,回滚,代码具有侵入性。下面是如何实现编程式事务
四,@Transactional详解
如前边所说,加了@Transactional注解之后,程序出现异常会自动回滚
但是如果手动捕获异常就依然会提交事务,如下代码
但是如果希望程序报异常,就手动再次手动抛异常,但是如果希望程序不报异常,而且希望事务回滚,就需要进行手动回滚
3.1 rollbackFor属性
@Transactional 默认只在遇到运行时异常和Error时才会回滚,非运行时异常不发生回滚
当代码中通过把rollbackFor属性设置为所有异常类型时候,无论发生什么种类异常,代码都会发生回滚
3.2 Spring事务传播机制,propagation属性
事务的传播机制用于控制方法嵌套调用时候,事务的传递,创建,挂起规则。
3.2.1 Propagation.REQUIRED
默认的事务传播级别,如果当前存在事务,则加入该事物,如果没有事务则创建一个新的事务
3.2.2 Propagation.SUPPORTS
如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务的方式继续运行
3.2.3 Propagation.MANDATORY
强制性,如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常
3.2.4 Propagation.REQUIRES_NEW
创建一个新的事物,如果当前存在事务,则把当前事务挂起,不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法都会新开启自己的事务,并且开启的事务相互独立,互不干扰
3.2.5 Propagation.NOT——SUPPORTED
以非事务的方式运行,如果当前存在事务,则把当前事务挂起。
3.2.6 Propagation.NEVER
以非事务的方式运行,如果当前存在事务,则抛出异常
3.3 Spring事务隔离级别 isolation属性
事务隔离级别有什么用:本质就是控制不同并发事务之间的读写可见性,规定当前事务能读到其他事务什么状态的数据,从而控制脏读,不可重复读,幻读是否出现。
并发事务三大脏问题:
1,脏读:读取到其他事物未提交的脏数据,对方回滚后数据失效
2,不可重复读:同一事务内,多次读取同一数据,可能会被其他事务修改更新
3,幻读:同一事务内,范围查询数据,被其他事务新增 / 删除数据,导致查询条数不一致
3.3.1 Isolation.DEFAULT
以连接的数据库的事务隔离级别为主
3.3.2 Isolation.READ_UNCOMMITTED
读未提交
3.3.3 Isolation.READ_COMMITTED
读已提交
3.3.4 Isolation.REPEATABLE_READ
可重复读
3.3.5 Isolation.SERIALIZABLE
串行化
四,Spring事务失效场景
场景一:注解加在非public方法上
失效原因:SpringAOP动态代理进拦截public方法,private / procted / 默认权限方法无法被代理,事务不生效
解决方法统一将事务方法定义为public
场景二:同类内部方法自调用
失效原因:本类中this.xxx()调用带@Transactional的方法,未经过Spring代理对象,AOP无法切入,事务失效。
解决方法:注入自身代理类调用
场景三:异常类型不匹配,默认不回滚
失效原因:Spring事务默认只回滚运行时异常和Error
解决方法:手动通过rollbackFor来指定异常类型,一般是rollbackFor = Exception.class
场景四:内部方法try-catch吞掉异常
失效原因:手动捕获异常不向外抛出,AOP切面无法感知异常,不会触发回滚
解决方法:手动再抛出异常或者手动调用事务回滚方法
场景五:事务方法被final / static修饰
失效原因:final / static方法无法被AOP动态代理重写,事务注解失效
解决方案:事务方法禁止加final,static修饰
场景六:只读事务执行写操作
失效原因:配置 readOnly = true 只读事务,执行增删改查操作会直接报错,事务失效
解决方法:查询方法只读事务,写业务关闭readOnly
场景七:多线程异步调用事务方法
失效原因:Spring事务基于线程本地变量ThreadLocal实现,多线程环境下线程上下文不同,事务无法传递,父子线程事务独立
解决方案:异步方法单独开启事务,不依赖主线程事务
五,底层原理:Spring事务执行核心流程
Spring事务核心:AOP动态代理 + ThreadLocal事务上下文 + 事务管理器
1,调用事务方法,Spring动态生成代理对象
2,切面前置拦截,根据传播机制判断是否新建事务
3,通过ThreadLocal绑定当前事务上下文(也就是调用方法往ThreadLocalMap往里面存数据)
4,执行目标业务方法
5,方法无异常,事务提交,有异常事务回滚
6.释放事务资源,清楚线程上下文。
补充:ThreadLocal是个啥??
Thread:每个线程实例内部有一个成员变量ThreadLocalMap threadLocals ,这是线程私有容器,别的线程访问不到。
ThreadLocalMap: 是Thread的静态内部类,本质上是一个自定义哈希表,专门存线程私有数据
ThreadLocal:对外提供set/get/remove方法的工具类,本身不存数据,只负责操作当前线程中的ThreadLocalMap
ThreadLocal会根据当前正在运行的线程首先拿到这个县城专属的ThreadLocalMap