前言
自己整理的spring 事务的读书笔记
参考
基本知识点
| 数据库隔离级别 | 说明 |
|---|---|
| ISOLATION_DEFAULT | 默认使用数据库的隔离级别 |
| ISOLATION_READ_UNCOMMITTED | 读未提交,存在脏读、幻读、不可重复读 |
| ISOLATION_READ_COMMITTED | 读已提交,存在幻读、不可重复读 |
| ISOLATION_REPEATABLE_READ | 可重复读,存在幻读(MySql通过MVCC解决这个问题) |
| ISOLATION_SERIALIZABLE | 串行化 |
| 事务问题名词解释 | 说明 |
|---|---|
| 脏读 | 一个事务读取到了另一个事务未提交的数据 |
| 幻读 | 当前事务查询是一条记录,另一个事务提交之后,再次查询是两条记录 |
| 不可重复读 | 一个事务读取一个值,当另一个事务提交对这个值修改的事务,当前事务再次读取发生了变化 |
| 事务传播机制 | 说明 |
|---|---|
| PROPAGATION_REQUIRED | 如果当前没有事务,则新建一个事务;反之,则用当前事务 |
| PROPAGATION_SUPPORTS | 如果当前有事务,则使用;反之,则无事务 |
| PROPAGATION_MANDATORY | 以非事务执行,如果当前有事务,则抛异常 |
| PROPAGATION_REQUIRES_NEW | 如果当前没有事务,则新建一个;反之,则把当前事务挂起,新建一个 |
| PROPAGATION_NOT_SUPPORTED | 如果当前有事务,则抛异常;反之,则以非事务运行 |
| PROPAGATION_NESTED | 如果当前已存在一个事务,那么该方法在嵌套事务中运行。 嵌套事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么行为和 PROPAGATION_REQUIRED一样(更具体可以看调用这块) |
| PROPAGATION_NEVER | 表示当前方法不运行在事务上下文中。如果当前正有一个 事务在运行,则会抛出异常 |
| @Transactional属性 | 说明 |
|---|---|
| value | 事务管理器 默认"transactionManager" |
| transactionManager | 事务管理器 |
| propagation | 传播机制 默认PROPAGATION_REQUIRED |
| isolation | 隔离基本 默认ISOLATION_DEFAULT |
| timeout | 超时时间 默认没有超时时间 |
| readOnly | 只读 会优化查询 默认false |
| rollbackFor | 回滚异常 默认runtimeExcpetion 和 error |
| noRollbackFor | 不回滚异常 |
参考: Spring事务
实例
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
- 开启事务
@SpringBootApplication(scanBasePackages = {"com.aop.test"})
@ServletComponentScan
@EnableAspectJAutoProxy(exposeProxy = true)
//开启事务
@EnableTransactionManagement
//mapper scan扫描的包下有service,会导致service重复添加
@MapperScan("com.aop.test.mapper")
public class AopApplication {
public static void main(String[] args) {
SpringApplication.run(AopApplication.class, args);
}
}
- Service实现类
@Service
public class TestDoorServiceImpl extends ServiceImpl<TestDoorMapper,TestDoor> implements ITestDoorService {
@Transactional(rollbackFor = Exception.class)
public boolean addTestDoor(TestDoor testDoor) {
boolean save = this.save(testDoor);
int i= 1/0;
return save;
}
/**
* 这种情况下调用就会失效,因为this不是代理的对象
* @param testDoor
* @return
*/
public boolean outFunction(TestDoor testDoor) {
//this.addTestDoor(testDoor);
return ((ITestDoorService)AopContext.currentProxy()).addTestDoor(testDoor);
}
}
- 调用
@RestController
@RequestMapping("/test")
public class MyController {
@Autowired
private ITestDoorService iTestDoorService;
@GetMapping("/addTest")
public Boolean saveTestDoor(){
TestDoor testDoor = new TestDoor();
testDoor.setOperatorId(22);
iTestDoorService.outFunction(testDoor);
return true;
}
}
初始化
@EnableTransactionManagement注解中:
- 引入
TransactionManagementConfigurationSelector proxyTargeClass标识是否用Cglib代理mode标识是使用JDK动态代理 还是使用AspectJ代理
在TransactionManagementConfigurationSelector,在PROXY模式下,通过Selector模式往容器中导入AutoProxyRegistrar 和 ProxyTransactionManagementConfiguration
在AutoProxyRegistrar中,往BeanDefinitionRegistry中注册了一个bean, 这个bean就是InfrastructureAdvisorAutoProxyCreator。
这个InfrastructureAdvisorAutoProxyCreator和在Aop中的AnnotationAwareAspectJAutoProxyCreator的继承体系是差不多的,当时Aop的AutoProxyCreator优先级更高,如果同时启用了事务和AOP,那么AnnotationAwareAspectJAutoProxyCreator替代它负责注册adviser和对符合条件的bean代理(其实就是AOP的过程)
在ProxyTransactionManagementConfiguration 在这个类中,注册了BeanFactoryTransactionAttributeSourceAdvisor增强器,而增强其依赖两个bean
TransactionAttributeSource(实际返回的是AnnotationTransactionAttributeSource)、
TransactionInterceptor。
TransactionAttributeSource的作用类似与PointCut,解析带有@Transactional注解。
TransactionInterceptor 实现了MethodInterceptor接口(MethodBeforeAdviceInterceptor、AspectJAfterAdvice、AfterReturningAdviceInterceptor和AspectJAfterThrowingAdvice),其实就是事务的通知(advice),采取是一种环绕式通知。
代理
代理过程其实和AOP类似,首先在初始化之前,找到了BeanFactoryTransactionAttributeSourceAdvisor增强器,然后再初始化之后执行时,匹配到复核Advisor切点的bean,对其进行代理。 详细可以文末链接
调用
我们在controller注入的接口,实际上它实现类,所有这个代理类是通过Cglib代理的。那么它就走的DynamicAdvisedInterceptor得invoke方法,拿到责任链,开始执行。
先调用得就是TransactionInterceptor得invoke方法。
- 获取
@Transactional注解信息 - 获得
TransactionManager事务管理器 - 创建
TransactionInfo事务信息 - 环绕式通知
- 如果有异常执行
- 最终清理事务信息
- 如果没有错误执行
如果当前不存在事务,则开始新建事务
如果存在事务,PROPAGATION_NOT_SUPPORTED则是挂起事务(把当前事务放入SuspendedResourcesHolder),以非事务运行。PROPAGATION_REQUIRES_NEW挂起事务,新建一个的事务。
如果是PROPAGATION_NESTED, 则是嵌套执行,如果事务管理器是Spring事务管理器,则会保存一个savePoint,然后用的同一个事务,如果是Jta事务,则返回一个新的事务。
在构建完成事务信息之后,就开始执行目标类得方法,执行成功,则走TransactionAspectSupport.commitTransactionAfterReturning方法,提交事务。
如果失败,则TransactionAspectSupport.completeTransactionAfterThrowing方法,如果异常是规定得异常或者是默认得异常(RuntimeExcpetion OR ERROR),则回滚。否则提交。
事务不生效场景
- Service方法抛出的异常不是RuntimeException或者Error类型,并且@Transactional注解上没有指定回滚异常类型
2.非事务方法直接通过this调用本类事务方法
这个在示例代码有体现,原因是this实际不是AOP代理的类,那么它的方法就不会执行增强的方法。那么可以这样在SpringBoot入口类中通过注解@EnableAspectJAutoProxy(exposeProxy = true)将当前代理对象暴露到AOP上下文中(通过AopContext的ThreadLocal实现)。这个在代理里有写。
同理可得,如果类似上面的这种this方法调用在一个本类的事务方法中调用,那么this方法上你定义的传播机制也不会生效,原因是一样的。如果要实现传播机制,也得使用上面的写法。
总结
Spring Transaction的执行大体是这样,更新详情的可以看看文末的文章,每一步都有详细的讲解。