前言
@Transactional 注解是我们在使用spring 相关内容时,经常需要使用的,网络上亦容易找到其使用方法和解析。我们在这里结合笔者的使用经验来,深入讨论一下 @Transactional 注解
一、理解 Spring 事务
我们在讨论Spring 的事务前,必须先了解计算机领域的“事务”,代表着什么含义。事务 其实就是一种机制,我们常说某某中间件支持“事务”,那么就代表他能实现事务的几种特性
事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
- 原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
- 一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- 隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
我们在Spring里说的事务,实际指的是在数据库操作上保持事务,然而这种事务的能力是源自于数据库本身,比如我们可以用 Mysql 的 innoDB 引擎,该引擎就支持事务。那么既然Spring本身和事务无关,为什么会谈到Spring事务呢 ?
实际上Spring事务指的是,在数据库支持事务的基础上,Spring可以通过简单的配置来控制事务的各个方面,最终达到我们业务上需要的效果。如果你把Spring去掉,手工使用jdbc去对接数据库,也能达到相同的效果,但是就需要大量额外的控制代码了
二、@Transactional 在哪里
我们首先必须要知道这个注解是哪个包提供的,实际上作为开发,我们很多时候使用spring系列都是全家桶,一口气引用了很多包。写代码就直接依赖,往往忽略了这个类或注解是谁提供的,这将模糊我们对开源组件的结构理解,个人认为是个不好的习惯。
我们可以看到,这个注解来源于Spring框架的 spring-tx 包,这个包的名字就已经说明这个包是专用来完成事务功能的
三、@Transactional 基本用法
在使用这个注解前,你必须得有一个spring 或者 springboot 工程,我们在这里以spingboot项目为例,其实因为springboot的自动配置机制,使得如果我们引用了starter-jdbc等包的情况下,会自动向容器中添加事务管理器,所以允许不在主类上标注 @EnableTransactionManagement,此处写是从Spring项目留下的习惯
@SpringBootApplication //开启事务 @EnableTransactionManagement public class TransactionalApplication { public static void main(String[] args) { SpringApplication.run(TransactionalApplication.class, args); } }
然后我们就可以在Spring 的 Bean 上使用@Transactional 注解了,我们来看该注解可以用在什么地方
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { ...... }
ElementType.METHOD, ElementType.TYPE 这两个标记表明了该注解可以使用在方法上和类上。我们可以提前说下两者各自的效果
- 用在方法上此方法具有事务属性,只有public修饰的方法才生效。protected,private和default修饰的方法上不生效,也不会抛出任何异常。
- 当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性
当然,从个人经验来看,两者各有利弊,注解在方法上自然是更灵活,但是如果是注解在接口方法中,又容易出现动态代理的问题,比如Springboot2 默认使用的 CGlib 动态代理,采用的是子类继承的方法,如果你在接口上使用,此时自然会失效乃至报错
四、@Transactional 配置详解
要想合理使用这个注解,我们还是得从注解本身的信息入手,我们先看一下这个注解提供了哪些属性
- value 和 transactionManager
- 它们两个是一样的意思。当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器
- propagation
- 事务的传播行为,默认值为 Propagation.REQUIRED
- isolation
- 事务的隔离级别,默认值为 Isolation.DEFAULT
- timeout
- 事务的超时时间,默认值为-1(不设置)。如果超过该时间限制但事务还没有完成,则自动回滚事务
- readOnly
- 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true
- rollbackFor
- 用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
- noRollbackFor
- 抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
- 那么接下来我将逐一讲解这些属性的作用。
1. 事务管理器
Spring 在事务管理器的处理上,是定义了一个接口 PlatformTransactionManager ,这个接口只有三个方法,也是我们最常用的关于事务的动作:
getTransaction 获取(创建)一个事务
commit 事务提交
rollback 事务回滚
然后 Spring 内置了大量的事务管理器的实现,为什么会有这么多种呢?主要还是不同的数据源情况,不同的传播需要,为了兼顾这些情况,所以此处可以手动去指定。最常用的,也是默认的事务管理器就是DataSourceTransactionManager
2. 传播行为
事务本身并没有传播概念,所谓的”传播“其实是Spring为了方便开发者管理事务而引入的概念。比如说我们开启了个事务,然后开始执行代码,但是java的代码往往会调用多个对象和方法,那些方法是否需要加入到事务中,或者使用新的事务,其实是需要人规定的。所以传播行为其实就是用来描述事务在多个方法间是怎样流转的
Spring 定义了7种传播规则,当然,我们需要知道,不是所有的事务管理器都支持7种规则,如HibernateTransactionManager 只支持三种REQUIRE 、 REQUIRE_NEW 、 NOT_SUPPORT
我们先简单介绍下这几种规则:
我可以举个例子,
比如我们经常苦恼rabbitMq消息 和 业务代码不构成事务,说发就发出去了,如果后续代码出现异常,发出去的消息却撤不回来。所以想把 “发mq消息” 和 “执行普通代码” 做成事务,策略是要发mq之前先把消息落库,如果普通代码执行成功,再发送消息。如果普通代码执行失败,触发回滚,落库的mq消息自然也会被回滚掉,mq消息取消发送。
不难发现,在这种场景下,就需要发这种mq消息时,必须处在一个事务里,因此,发送这种mq的方法就可以加上@Transactional注解,传播方式则是MANDATORY