1. 事务特点
- 原子性:一个事务中所有对数据库的操作是一个不可分割的操作序列,要么全做要么全不做
- 一致性:数据不会因为事务的执行而遭到破坏
- 隔离性:一个事务的执行,不受其他事务的干扰,即并发执行的事务之间互不干扰
- 持久性:一个事务一旦提交,它对数据库的改变就是永久的。
2. 事务实现机制
Spring为事务管理提供了丰富的功能支持。Spring 事务管理分为编程式和声明式的两种方式。
编程式事务管理: 编程式事务管理使用 TransactionTemplate 或者直接使用底层的 PlatformTransactionManager。对于编程式事务管理,spring 推荐使用 TransactionTemplate。
声明式事务管理: 建立在 AOP 之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务管理不需要入侵代码,更快捷而且简单,推荐使用。声明式事务有两种方式:
-
-
- 一种是在配置文件(xml)中做相关的事务规则声明(因为很少用本文不讲解)
- 另一种是基于 @Transactional 注解的方式。注释配置是目前流行的使用方式,推荐使用。
-
Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,以 CglibAopProxy 为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。对于 JdkDynamicAopProxy,需要调用其 invoke 方法。
3. 注解 @Transactional 的使用
3.1. 注解 @Transactional 常用配置
参 数 名 称 | 功 能 描 述 |
readOnly | 用于设置当前事务是否为只读事务,设置为 true 表示只读,false 则表示可读写,默认值为 false。例如:@Transactional(readOnly=true) |
rollbackFor | 用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class);指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
transactionManager / value | 多个事务管理器托管在 Spring 容器中时,指定事务管理器的 bean 名称 |
rollbackForClassName | 用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称 @Transactional(rollbackForClassName=”RuntimeException”) 指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”}) |
noRollbackFor | 用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
noRollbackForClassName | 用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”) 指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”, ”Exception”}) |
propagation | 用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED, readOnly=true) |
isolation | 用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 |
timeout | 该属性用于设置事务的超时秒数,默认值为 - 1 表示永不超时 |
3.2. Propagation 的属性(事务的传播行为)
@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
Propagation 属性 | 含义 |
REQUIRED | 默认值 在有 transaction 状态下执行;如当前没有 transaction,则创建新的 transaction; |
SUPPORTS | 如当前有 transaction,则在 transaction 状态下执行;如果当前没有 transaction,在无 transaction 状态下执行; |
MANDATORY | 必须在有 transaction 状态下执行,如果当前没有 transaction,则抛出异常 IllegalTransactionStateException; |
REQUIRES_NEW | 创建新的 transaction 并执行;如果当前已有 transaction,则将当前 transaction 挂起; |
NOT_SUPPORTED | 在无 transaction 状态下执行;如果当前已有 transaction,则将当前 transaction 挂起; |
NEVER | 在无 transaction 状态下执行;如果当前已有 transaction,则抛出异常 IllegalTransactionStateException。 |
3.3. 事务 5 种隔离级别
例如:@Transactional(isolation = Isolation.READ_COMMITTED)
隔离级别 | 含义 |
DEFAULT | 这是一个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别另外四个与 JDBC 的隔离级别相对应; |
READ_UNCOMMITTED | 最低的隔离级别。事实上我们不应该称其为隔离级别,因为在事务完成前,其他事务可以看到该事务所修改的数据。而在其他事务提交前,该事务也可以看到其他事务所做的修改。可能导致脏,幻,不可重复读 |
READ_COMMITTED | 大多数数据库的默认级别。在事务完成前,其他事务无法看到该事务所修改的数据。遗憾的是,在该事务提交后,你就可以查看其他事务插入或更新的数据。这意味着在事务的不同点上,如果其他事务修改了数据,你就会看到不同的数据。可防止脏读,但幻读和不可重复读仍可以发生。 |
REPEATABLE_READ | 比 ISOLATION_READ_COMMITTED 更严格,该隔离级别确保如果在事务中查询了某个数据集,你至少还能再次查询到相同的数据集,即使其他事务修改了所查询的数据。然而如果其他事务插入了新数据,你就可以查询到该新插入的数据。可防止脏读,不可重复读,但幻读仍可能发生。 |
SERIALIZABLE | 完全服从 ACID 的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。代价最大、可靠性最高的隔离级别,所有的事务都是按顺序一个接一个地执行。避免所有不安全读取。 |
3.4. 防止事务失效
在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。
@Transactional 注解应该只被应用在 public 修饰的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 该注解,它也不会报错(IDEA 会有提示), 但事务并没有生效。
被外部调用的公共方法 A 有两个进行了数据操作的子方法 B 和子方法 C 的事务注解说明:
被外部调用的公共方法 A 未声明事务 @Transactional,子方法 B 和 C 若是其他类的方法且各自声明事务,则事务由子方法 B 和 C 各自控制
被外部调用的公共方法 A 未声明事务 @Transactional,子方法 B 和 C 若是本类的方法,则无论子方法 B 和 C 是否声明事务,事务均不会生效
被外部调用的公共方法 A 声明事务 @Transactional,无论子方法 B 和 C 是不是本类的方法,无论子方法 B 和 C 是否声明事务,事务均由公共方法 A 控制
被外部调用的公共方法 A 声明事务 @Transactional,子方法运行异常,但运行异常被子方法自己 try-catch 处理了,则事务回滚是不会生效的!
如果想要事务回滚生效,需要将子方法的事务控制交给调用的方法来处理:
方案 1:子方法中不用 try-catch 处理运行异常
方案 2:子方法的 catch 里面将运行异常抛出【throw new RuntimeException();】
默认情况下,Spring 会对 unchecked 异常进行事务回滚,也就是默认对 RuntimeException() 异常或是其子类进行事务回滚。
如果是 checked 异常则不回滚,例如空指针异常、算数异常等会被回滚;文件读写、网络问题 Spring 就没法回滚。
若想对所有异常(包括自定义异常)都起作用,注解上面需配置异常类型:@Transactional(rollbackFor = Exception.class
数据库要支持事务,如果是 mysql,要使用 innodb 引擎,myisam 不支持事务
事务 @Transactional 由 spring 控制时,它会在抛出异常的时候进行回滚。如果自己使用 try-catch 捕获处理了,是不生效的。如果想事务生效可以进行手动回滚或者在 catch 里面将异常抛出【throw new RuntimeException();】
方案一:手动抛出运行时异常(缺陷是不能在 catch 代码块自定义返回值)
try{
....
}catch(Exception e){
logger.error("",e);
throw new RuntimeException(e);
}
方案二:手动进行回滚【TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 】
try{
...
}catch(Exception e){
log.error("fail",e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return false;
}
@Transactional 可以放在 Controller 下面直接起作用,看到网上好多同学说要放到 @Component 下面或者 @Service 下面,经过试验,可以不用放在这两个下面也起作用。
@Transactional 引入包问题,它有两个包:
import javax.transaction.Transactional;
// 和
import org.springframework.transaction.annotation.Transactional; // 推荐