Ref: https://www.cnblogs.com/duanxz/p/3439387.html
1.概览
事务管理对于企业应用来说是至关重要的,好使出现异常情况,它也可以保证数据的一致性。
spring 支持编程式事务管理和声明式事务管理两种方式:
- 编程式事务管理使用 TransactionTemplate 或者直接使用底层的 PlatformTransactionManager。对于编程式事务管理,spring 推荐使用 TransactionTemplate。
- 声明式事务管理建立在 AOP 之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明 (或通过基于 @Transactional 注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是 spring 倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的 POJO 对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是其最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
声明式事务管理也有两种常用的方式,一种是基于 tx 和 aop 名字空间的 xml 配置文件,另一种就是基于 @Transactional 注解。显然基于注解的方式更简单易用,更清爽。
2.Spring 事务特性
spring 所有的事务管理策略类都继承自 org.springframework.transaction.PlatformTransactionManager 接口
其中 TransactionDefinition 接口定义以下特性:
事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是 TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如 PostgreSQL 实际上并没有此级别。
- TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在 TransactionDefinition 定义中包括了如下几个表示传播行为的常量:
TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。(注意这里,不要采坑)
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。
PROPAGATION_REQUIRES_NEW 与 PROPAGATION_NESTED 的区别
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于:
PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.
事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是 none,没有超时限制。
事务只读属性
只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用 Hibernate 的时候。<br /> “只读事务” 并不是一个强制选项,它只是一个 “暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么 JDBC 驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。 <br />但是你非要在 “只读事务” 里面修改数据,也并非不可以,只不过对于数据一致性的保护不像 “读写事务” 那样保险而已。 <br />因此,“只读事务” 仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可。
Spring 事务回滚规则
指示 spring 事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring 事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。默认配置下,spring 只有在抛出的异常为运行时 unchecked 异常时才回滚该事务,也就是抛出的异常为 RuntimeException 的子类 (Errors 也会导致事务回滚),而抛出 checked 异常则不会导致事务回滚。可以明确的配置在抛出那些异常时回滚事务,包括 checked 异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过 setRollbackOnly () 方法来指示一个事务必须回滚,在调用完 setRollbackOnly () 后你所能执行的唯一操作就是回滚。
示例:基于注解的声明式事务管理配置 @Transactional
spring.xml
添加 tx 名字空间
MyBatis 自动参与到 spring 事务管理中,无需额外配置,只要 org.mybatis.spring.SqlSessionFactoryBean 引用的数据源与 DataSourceTransactionManager 引用的数据源一致即可,否则事务管理会不起作用。
@Transactional 注解
使用 @Transactional 时,可以指定如下属性:
a、isolation:用于指定事务的隔离级别。默认为底层事务的隔离级别。
b、noRollbackFor:指定遇到指定异常时强制不回滚事务。
c、noRollbackForClassName:指定遇到指定多个异常时强制不回滚事务。该属性可以指定多个异常类名。
d、propagation: 指定事务的传播属性。
e、readOnly:指定事务是否只读。
f、rollbackFor:指定遇到指定异常时强制回滚事务。
g、rollbackForClassName:指定遇到指定多个异常时强制回滚事务。该属性可以指定多个异常类名。
h、timeout:指定事务的超时时长。
事务可回滚要注意点
1、被注解的必须是 public
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
poxy-target-class=”true”表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。
2、rollbackFor 和 noRollbackFor
需我们指定方式来让事务回滚 :
要想所有异常都回滚,要加上 @Transactional (rollbackFor={Exception.class, 其它异常})
如果让 unchecked 例外不回滚:@Transactional (notRollbackFor=RunTimeException.class)
关于 rollbackFor 配置的经历
当的 Transactional 中配置 rollbackFor = Exception.class 时,抛出 RuntimeException 时是会回滚的。但是如果是 Unchecked Exceptions 则不会回滚。
于是查看 Spring 的 Transactional 的 API 文档,发现下面这段:
If no rules are relevant to the exception, it will be treated like DefaultTransactionAttribute (rolling back on runtime exceptions).
后面又试了下发现,如果不添加 rollbackFor 等属性,Spring 碰到 Unchecked Exceptions 都会回滚,不仅是 RuntimeException,也包括 Error。
3、来自外部的方法调用才会被 AOP 代理捕获
默认情况下,只有来自外部的方法调用才会被 AOP 代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用 @Transactional 注解进行修饰。(我碰到后,只有重构代码再引入一层 service 解决)
示例一:不要 rollbackFor,unchecked 异常,可以回滚
示例二:noRollbackFor 使用场景
@Transactional(noRollbackFor=RuntimeException.class)
@Override
public void insert(Test test) {
dao.insert(test);
// 抛出unchecked异常,触发事务,noRollbackFor=RuntimeException.class,不回滚
throw new RuntimeException("test");
}
示例三:当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性。
@Transactional
public class MyBatisServiceImpl implements MyBatisService {
@Autowired
private MyBatisDao dao;
@Override
public void insert(Test test) {
dao.insert(test);
// 抛出unchecked异常,触发事务,回滚
throw new RuntimeException("test");
}
}
示例四:propagation=Propagation.NOT_SUPPORTED
@Transactional(propagation=Propagation.NOT_SUPPORTED)
@Override
public void insert(Test test) {
// 事务传播行为是PROPAGATION_NOT_SUPPORTED,以非事务方式运行,不会存入数据库
dao.insert(test);
}