概述
众所周知,事务是来自数据库的概念,而Spring只是通过事务管理器PlatformTransactionManager来对数据库事务提供了支持,其主要的功能有三个:获取事务、提交以及回滚。Spring对数据库事务的支持主要包括两方面,分别是事务的隔离级别和传播机制。其中,Spring对于事务隔离级别没有做过多的改动,就是通过简单的配置沿用了数据库隔离级别的配置,而Spring的事务传播机制则是为解决事务嵌套等复杂场景而设计的一套事务管理机制,这个是Spring事务在应用中比较复杂的东西。本文将通过举例来分析说明Spring事务管理的基本原理和不同传播机制的使用场景。
JDBC事务管理的过程
- 过程:获取数据库连接-->开启事务-->执行sql-->提交/回滚
- 这是数据库底层在开启事务的过程都要执行的4步操作,不管是对于jdbc还是spring来说都是不可避免的。Spring的事务则是给我们提供了功能更为完善的API和注解,以方便我们在程序开发的过程中实现对数据库事务的控制。
Connection conn = DriverManager.getConnection("");
conn.setAutoCommit(false);
statement = conn.createStatement();
statement.executeQuery("");
conn.commit();
//conn.rollback(); //提交或者回滚
Spring事务的实现方式
编程式事务
编程式事务的实现主要依赖三个核心API:PlatformTransactionManager,TransactionDefintion,TransactionStatus
- PlatformTransactionManager:事务管理器,负责数据库连接的创建,事务的创建,提交和回滚。通过ThreadLocal来保存当前数据库事务的连接信息,根据传播机制来决定是否要挂起当前事务和恢复上一层事务,从而实现嵌套事务。
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}
- TransactionStatus:事务状态,可以理解成它就是一个事务对象,事务的创建、提交/回滚都是通过事务管理器的入参指定创建、提交/回滚某个事务,而此入参就是TransactionStatus类型,故其可以直接理解成就是一个事务对象。
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
boolean hasSavepoint();
void flush();
}
- TransactionDefintion:事务定义,定义事务的属性,如隔离级别、传播机制、事务超时时间等。
public interface TransactionDefinition {
//此处省略成员变量的定义
default int getPropagationBehavior() {
return 0;
}
default int getIsolationLevel() {
return -1;
}
default int getTimeout() {
return -1;
}
default boolean isReadOnly() {
return false;
}
@Nullable
default String getName() {
return null;
}
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}
- 编程式事务编写示例,伪代码:
@Resource
PlatformTransactionManager txManager;
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try{
//业务DAO操作,一组Sql语句
...
//事务提交
txManager.commit(status);
return;
}catch (Exception e){
log.error(errorMessage);
//抓到异常,事务回滚
txManager.rollback(status);
return;
}
- 除了用PlatformTransactionManager之外,还可以使用TransactionTemplate来代替。
声明式事务
通过@Transactional注解到指定的Public修饰的方法,可在注解内指定事务的属性(隔离级别,回滚机制,隔离级别等),在启动类上加上@EnableTransactionManagement注解表示可以开启事务。
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
}
- 相比编程式事务,声明式事务的使用更加简单,代码的入侵性也更小,复用性也更高。但是,古人曾经说过,外表越简单的东西,其背后往往就更复杂。
Spring事务的基本执行原理
- 从Spring事务的两种方式中,我们可以看到,编程式事务的使用过程:获取事务管理器-->定义事务属性-->新建事务状态(事务对象)-->提交/回滚事务。声明式事务的使用过程:给要加上事务控制的方法加上@Transactional注解。这两种方式看起来都没有像JDBC的事务控制那样,需要去做建立连接等工作,这是因为Spring在启动的时候已经通过AOP的方式将这些基础操作织入到我们的业务代码中了。
- 至于AOP的原理在这里不展开叙述,一句话概述,AOP是通过代理类(jdk动态代理、Cglib动态代理)来实现业务对象的功能增强的,也就是被事务AOP处理过(加了@Transactional注解的bean都会被切)的Bean会生成一个代理对象,该代理对象在调用原对象的业务方法之前就把数据库建立连接、开启事务等工作都做了,执行完业务方法之后则帮我们把事务提交/回滚都做了。
- 就如下面的代码,在test()方法内直接调用testB()方法,即便testB()方法标注了@Tranactional注解,也会失效,因为这相当于this.testB(),与Spring毫无关系。修改方式:注入自身GoodsService(代理对象),通过goodsService.testB()去调用。
@Component
public class GoodsService {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
GoodsService goodsService;
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
//sql1,sql2
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
//testB();//直接调用,事务失效,本质就是this.testB();是本对象代码的直接调用
goodsService.testB(); //生效,注入的是GoodsService的代理对象,增强获得了事务管理的功能
sql3;
}
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(33,'bbb',600) ");
}
}
总结下来,Spring事务基本执行过程如下:
- 利用事务管理器PlatformTransactionManager事务管理器新建一个数据库连接;
- 修改数据库连接的autocommit为false;
- 执行业务方法,其中会执行sql;
- 如果没有抛异常,则提交;
- 如果抛了异常,则回滚。
Spring事务的传播机制
- 在开发的过程中,会遇到一个方法调用另一个方法,如上面代码中的test()和testB()的调用,这时候就会存在以下问题:
- test()和testB()需要在同一个事务吗?
- test()和testB()需要单独的事务吗?
- 等等问题
- Spring的传播机制就是为了对方法间调用的事务嵌套场景提供支撑,先看一下一种事务嵌套的场景
嵌套事务的执行过程,还是以上面代码为例:
-
获取Spring事务管理器,创建数据库连接conn
-
conn.autocommit = false;
-
设置conn的隔离级别
-
将conn放入到ThreadLocal<Map<Object,Object>>,key是DataSource,value是数据库连接conn,保证同一个事务中每个sql拿到的是同一个连接
-
执行target.test(),sql1、sql2...
-
如果执行testB()的时候要开启一个新事务:
- 挂起:将挂起对象.conn连接暂存起来
- 获取Spring事务管理器,创建数据库连接conn1;
- conn1.autocommit = false;
- 设置conn1的隔离级别
- 将conn1放入到ThreadLocal<Map<Object,Object>> key是DataSource,value是数据库连接conn1
- 执行testB()的sql
- conn1.提交
- 恢复:获取挂起对象.conn,放入ThreadLocal<Map<Object,Object>>中
- rollback():如果内部方法的事务发送回滚,则将ThreadLocal的全局回滚标记为true
-
拿到属于test()方法原先的数据库连接,执行sql3
-
test()方法执行完了之后,从ThreadLocal中拿到原连接进行提交/回滚
-
核心过程:在执行某个方法的时候,先判断当前方法是否存在事务,就是判断当前线程的ThreadLocal中是否存在一个数据库连接,如果存在则说明当前已经有一个事务了。
Spring传播机制分类
int PROPAGATION_REQUIRED = 0; //Spring默认的传播机制,表示当前如果没有事务,则新建一个事务,如果当前有事务则加入该事务中。
int PROPAGATION_SUPPORTS = 1; //如果当前存在事务,则加入当前事务,如果不存在事务,则以非事务的方式执行。
int PROPAGATION_MANDATORY = 2; //当前存在事务,则加入当前事务;当前不存在事务,则抛出异常。
int PROPAGATION_REQUIRES_NEW = 3;//创建一个事务,如果当前有事务则将该事务挂起。两个事务互不影响,回滚的时候也只会回滚各自的事务。
int PROPAGATION_NOT_SUPPORTED = 4; //不支持事务,如果当前存在事务,则将当前事务事务挂起,自己以非事务的方式执行。如果不存在事务,则以非事务的方式执行。
int PROPAGATION_NEVER = 5; //如果当前没有事务存在,就以非事务方式执行;如果有,就抛出异常。
int PROPAGATION_NESTED = 6; //当前存在事务,则加入当前事务,抛异常不会全部回滚,只会回滚自身方法设计的sql,相当于数据库事务中加了个savepoint
- 以非事务方式运行:表示以非Spring事务的方式运行,即不会通过Spring的事务管理器去建立连接,由jdbcTemplate或者Mybatis自己去建立连接执行sql。
- 传播机制为REQUIRED:Spring默认的传播机制,表示当前如果没有事务,则新建一个事务,如果当前有事务则加入该事务中。该场景的执行流程如下:
- 情況1:
- 新建一个数据库连接conn
- 设置conn.autocommit=false;
- 执行test()方法中的sql;
- 执行testB()方法中的sql;
- 提交事务commit()。
@Component
public class GoodsService {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
GoodsService goodsService;
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
goodsService.testB();
}
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
}
}
- 情況2:
- 新建一个数据库连接conn
- 设置conn.autocommit=false;
- 执行test()方法中的sql;
- 执行testB()方法中的sql;
- test方法抛出异常
- 执行conn的rollback()方法进行回滚,两个方法的事务都不会提交
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
goodsService.testB();
throw new RuntimeException("test方法抛异常");
}
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
}
- 情况3:
- 新建一个数据库连接conn;
- 设置conn.autocommit=false;
- 执行test()方法中的sql;
- 执行testB()方法中的sql;
- testB方法抛出异常
- 执行conn的rollback方法进行回滚,两个方法的事务都回滚
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
goodsService.testB();
}
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
throw new RuntimeException("testB方法抛出异常");
}
- 传播机制为REQUIRES_NEW:创建一个事务,如果当前有事务则将该事务挂起。两个事务互不影响,回滚的时候也只会回滚各自的事务。该场景的执行流程如下:
- 情况4:
- 新建一个数据库连接conn
- 设置conn.autocommit=false;
- 执行test()方法中的sql;
- 新建一个数据库连接conn1;
- 设置conn1.autocommit=false;
- 执行testB()方法的sql;
- 抛出异常
- 执行conn1的rollback方法进行回滚
- test()方法接收到来自testB()的异常,然后抛出异常
- 执行conn的rollback方法进行回滚
- 最终两个事务都会回滚
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
goodsService.testB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
throw new RuntimeException("testB方法抛出异常");
}
- 情况5:
- 新建一个数据库连接conn
- 设置conn.autocommit=false;
- 执行test()方法中的sql;
- 新建一个数据库连接conn1;
- 设置conn1.autocommit=false;
- 执行testB()方法的sql;
- 抛出异常
- 执行conn1的rollback方法进行回滚
- test()方法接收到来自testB()的异常,catch住了异常,但没有把异常继续往外抛
- test()方法没有收到异常,执行conn的commit方法提交事务
- 最终test()方法的事务提交了,testB()方法的事务回滚了
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
try{
goodsService.testB();
}catch (Exception e){
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
throw new RuntimeException("testB方法抛出异常");
}
- 情况6:基于情况5的条件下,如果希望testB()抛异常之后test方法的事务也能回滚,而又喜欢能catch住异常,以便返回友好的提示信息,可以设置事务强制回滚:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
try{
goodsService.testB();
}catch (Exception e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(24,'sss',600) ");
throw new RuntimeException("testB方法抛出异常");
}
- 传播机制为SUPPORTS:如果当前存在事务,则加入当前事务,如果不存在事务,则以非事务的方式执行。
- 情况7:
- 新建一个数据库连接conn
- 设置conn.autocommit=false;
- 执行test()方法中的sql;
- 执行testB()方法中的sql;
- 提交事务commit()。
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
goodsService.testB();
}
@Transactional(propagation = Propagation.SUPPORTS,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(24,'sss',600) ");
}
- 情况8:以非事务的方式进行,即通过jdbcTemplate建立数据库连接执行sql
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
goodsService.testB();
}
@Transactional(propagation = Propagation.SUPPORTS,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(24,'sss',600) ");
}
- 传播机制为NOT_SUPPORTED:不支持事务,如果当前存在事务,则将当前事务事务挂起,自己以非事务的方式执行。如果不存在事务,则以非事务的方式执行。
- 情况9:
- 新建一个数据库连接conn
- 设置conn.autocommit=false;
- 执行test()方法中的sql;
- 挂起conn的事务
- 执行testB()方法中的sql;
- 执行conn的commit方法提交事务。
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
goodsService.testB();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(24,'sss',600) ");
}
- 传播机制为MANDATORY:当前存在事务,则加入当前事务;当前不存在事务,则抛出异常。
- 情况10:
- 新建一个数据库连接conn
- 设置conn.autocommit=false;
- 执行test()方法中的sql;
- 执行testB()方法中的sql;
- 执行conn的commit方法提交事务。
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
goodsService.testB();
}
@Transactional(propagation = Propagation.MANDATORY,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(24,'sss',600) ");
}
- 传播机制为NESTED:嵌套事务,当前存在事务,则加入当前事务,抛异常不会全部回滚,只会回滚自身方法设计的sql,相当于数据库事务中加了个savepoint b,testB()方法抛异常只会回滚到b点。
- 情况11:
- 新建一个数据库连接conn
- 设置conn.autocommit=false;
- 执行test()方法中的sql;
- 执行testB()方法中的sql;
- testB抛异常,testB方法的sql回滚
- 执行conn的commit方法提交test方法的sql。
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
jdbcTemplate.execute("insert into goods values(56,'sss',500) ");
goodsService.testB();
}
@Transactional(propagation = Propagation.NESTED,rollbackFor = Exception.class)
public void testB(){
jdbcTemplate.execute("insert into goods values(24,'sss',600) ");
}
- 传播机制为NEVER:如果当前没有事务存在,就以非事务方式执行;如果有,就抛出异常。
总结
本文简述了Spring事务的基本原理,但涉及底层实现原理AOP层面没有过多地进行分析,最后结合代码例子去分析了不同事务传播机制下的执行过程,主要掌握REQUIRED和REQUIRED_NEW这两种传播机制。