Spring系列之事务管理-16
事务管理
因为Spring的事务管理不需与任何特定的事务API耦合,并且其提供了两种事务管理方式:编程式事务管理和声明式事务管理。对不同的持久层访问技术,编程式事务提供一致的事务编程风格,通过模板化的操作一致性地管理事务;而声明式事务基于Spring AOP实现,却并不需要程序开发者成为AOP专家,亦可轻易使用Spring的声明式事务管理。
事务抽象
Spring 事务抽象的关键是事务策略的概念。事务策略由 定义TransactionManager
所谓事务管理,实质上就是按照给定的事务规则来执行提交或者回滚操作,其中,“给定的事务规则”是用 TransactionDefinition 表示的,“按照……来执行提交或者回滚操作”是用 PlatformTransactionManager 表示的,而 TransactionStatus 可以看作代表事务本身。
TransactionDefinition
接口指定:
- 传播:通常,事务范围内的所有代码都在该事务中运行。但是,您可以指定在事务上下文已存在时运行事务方法时的行为。例如,代码可以在现有事务中继续运行(常见情况),或者可以暂停现有事务并创建新事务。
- 隔离:此事务与其他事务的工作隔离的程度。例如,这个事务可以看到来自其他事务的未提交的写入吗?
- 超时:就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。
- 只读状态:当您的代码读取但不修改数据时,您可以使用只读事务。在某些情况下,只读事务可能是一种有用的优化,例如当您使用 Hibernate 时。
TransactionStatus 接口提供了一个简单的控制事务执行和查询事务状态的方法。
声明式事务管理
Spring 的声明式事务管理是建立在 Spring AOP 机制之上的,其本质是对目标方法前后进行拦截,并在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
显然,声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要在XML文件中配置或者添加注解就可以获得完全的事务支持。
除了基于命名空间的事务配置方式,Spring 还引入了基于 Annotation 的方式,具体主要涉及@Transactional 标注。@Transactional 可以作用于接口、接口方法、类以及类方法上:当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性;当作用于方法上时,该标注来覆盖类级别的定义。如下所示:
事务的传播性:
@Transactional(propagation=Propagation.REQUIRED)
事务的隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
只读:
@Transactional(readOnly=true)
该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。
事务的超时性:
@Transactional(timeout=30)
回滚:
指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
Spring为了同时支持JDBC和JTA两种事务模型,就抽象出PlatformTransactionManager
。因为我们的代码只需要JDBC事务,因此,在AppConfig
中,需要再定义一个PlatformTransactionManager
对应的Bean,它的实际类型是DataSourceTransactionManager
:
@Configuration
@ComponentScan
@PropertySource("jdbc.properties")
public class AppConfig {
...
@Bean
PlatformTransactionManager createTxManager(@Autowired DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
使用编程的方式使用Spring事务仍然比较繁琐,更好的方式是通过声明式事务来实现。使用声明式事务非常简单,除了在AppConfig
中追加一个上述定义的PlatformTransactionManager
外,再加一个@EnableTransactionManagement
就可以启用声明式事务:
@Configuration
@ComponentScan
@EnableTransactionManagement // 启用声明式
@PropertySource("jdbc.properties")
public class AppConfig {
...
}
然后,对需要事务支持的方法,加一个@Transactional
注解:
@Component
public class UserService {
// 此public方法自动具有事务支持:
@Transactional
public User register(String email, String password, String name) {
...
}
}
或者更简单一点,直接在Bean的class
处加上,表示所有public
方法都具有事务支持:
@Component
@Transactional
public class UserService {
...
}
回滚事务
默认情况下,如果发生了RuntimeException
,Spring的声明式事务将自动回滚。在一个事务方法中,如果程序判断需要回滚事务,只需抛出RuntimeException
,例如:
@Transactional
public buyProducts(long productId, int num) {
...
if (store < num) {
// 库存不够,购买失败:
throw new IllegalArgumentException("No enough products");
}
...
}
如果要针对Checked Exception回滚事务,需要在@Transactional
注解中写出来:
@Transactional(rollbackFor = {RuntimeException.class, IOException.class})
public buyProducts(long productId, int num) throws IOException {
...
}
上述代码表示在抛出RuntimeException
或IOException
时,事务将回滚。
为了简化代码,我们强烈建议业务异常体系从RuntimeException
派生,这样就不必声明任何特殊异常即可让Spring的声明式事务正常工作:
public class BusinessException extends RuntimeException {
...
}
事务边界
在使用事务的时候,明确事务边界非常重要。
@Component
public class UserService {
@Autowired
BonusService bonusService;
@Transactional
public User register(String email, String password, String name) {
// 插入用户记录:
User user = jdbcTemplate.insert("...");
// 增加100积分:
bonusService.addBonus(user.id, 100);
}
}
现在问题来了:调用方(比如RegisterController
)调用UserService.register()
这个事务方法,它在内部又调用了BonusService.addBonus()
这个事务方法,一共有几个事务?如果addBonus()
抛出了异常需要回滚事务,register()
方法的事务是否也要回滚?
事务传播
要解决上面的问题,我们首先要定义事务的传播模型。
UserService.register()
已经开启了一个事务,那么在内部调用BonusService.addBonus()
时,BonusService.addBonus()
方法就没必要再开启一个新事务,直接加入到BonusService.register()
的事务里就好了。
因此,Spring的声明式事务为事务传播定义了几个级别,默认传播级别就是REQUIRED,它的意思是,如果当前没有事务,就创建一个新事务,如果当前有事务,就加入到当前事务中执行。
默认的事务传播级别是REQUIRED
,它满足绝大部分的需求。还有一些其他的传播级别:
SUPPORTS
:表示如果有事务,就加入到当前事务,如果没有,那也不开启事务执行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务;
MANDATORY
:表示必须要存在当前事务并加入执行,否则将抛出异常。这种传播级别可用于核心更新逻辑,比如用户余额变更,它总是被其他事务方法调用,不能直接由非事务方法调用;
REQUIRES_NEW
:表示不管当前有没有事务,都必须开启一个新的事务执行。如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行;
NOT_SUPPORTED
:表示不支持事务,如果当前有事务,那么当前事务会挂起,等这个方法执行完成后,再恢复执行;
NEVER
:和NOT_SUPPORTED
相比,它不但不支持事务,而且在监测到当前有事务时,会抛出异常拒绝执行;
NESTED
:表示如果当前有事务,则开启一个嵌套级别事务,如果当前没有事务,则开启一个新事务。
上面这么多种事务的传播级别,其实默认的REQUIRED
已经满足绝大部分需求,SUPPORTS
和REQUIRES_NEW
在少数情况下会用到,其他基本不会用到,因为把事务搞得越复杂,不仅逻辑跟着复杂,而且速度也会越慢。
推荐阅读
-
spring基础系列之JavaConfig配置详解
-
spring-boot-2.0.3不一样系列之番外篇 - 自定义session管理,绝对有值得你看的地方
-
详解Spring学习之编程式事务管理
-
Spring Cloud 系列之 Spring Cloud Stream
-
Spring系列之——springboot解析resources.application.properties文件
-
Spring5.0源码学习系列之浅谈懒加载机制原理
-
spring-boot-2.0.3不一样系列之番外篇 - springboot事件机制,绝对有值得你看的地方
-
spring-boot-2.0.3不一样系列之源码篇 - SpringApplication的run方法(一)之SpringApplicationRunListener,绝对有值得你看的地方
-
Spring系列之——spring security
-
Spring Data 系列之JPA(二)