# transaction-example **Repository Path**: cwbshare/transaction-example ## Basic Information - **Project Name**: transaction-example - **Description**: spring事务实战讲解 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2020-04-24 - **Last Updated**: 2021-03-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # spring事务实战讲解 ### 事务概念 事务是由多个操作组成的一个单元,在执行中,要么全部成功,要么全失败。 ### spring事务注解 #### @Transactional等价于@Transactional(propagation = Propagation.REQUIRED) 如果当前线程中存在事务,则使用该事务执行,如果不存在事务,则新建一个事务。 #### @Transactional(propagation = Propagation.REQUIRES_NEW) - 如果当前线程中存在事务,则挂起当前事务,并且新建一个事务继续执行,新事务执行完毕之后,唤醒之前挂起的事务,继续执行。 - 如果当前线程不存在事务,则新建一一个事务继续执行。 ### spring事务实例 `说明:为了简化代码,下面示例使用了更新的操作来演示,而项目实际代码中,则是使用新增记录来做,因为每次清了数据库,再运行测试用例,看得比较明白。` #### (1)皆无@Transactional注解 ```java @Service public class A{ @Autowired private AMapper aMapper; @Autowired private B b; public void testA(){ aMapper.update(1,'A'); b.testB(); // 制造异常 int x = 1 / 0; } } ``` ```java @Service public class B{ @Autowired private BMapper bMapper; public void testB(){ bMapper.update(1, 'B'); } } ``` ##### 测试结果 testA方法单元测试结果:方法抛出异常,但aMapper和bMapper数据库操作皆成功。 ##### 原因分析 testA和testB方法都没有加@Transactional注解,那么它们的方法使用的是非事务的SqlSession来执行,那么它的操作会由数据库默认设置的自动提交的方式来更新数据库,所以只要他们将SQL发到数据库上操作后,就已经将结果提交,故testA最后一行才发生异常,也不会引起数据库的回滚。 #### (2)调用者有@Transactional注解,被调用者无@Transactional注解 ##### 场景1 ```java @Service @Slf4j public class A{ @Autowired private AMapper aMapper; @Autowired private B b; @Transactional public void testA(){ aMapper.update(1,'A'); // 不处理异常 b.testB(); } } ``` ```java @Service public class B{ @Autowired private BMapper bMapper; public void testB(){ bMapper.update(1, 'B'); // 制造异常 int x = 1 / 0; } } ``` ##### 测试结果 testA方法单元测试结果:方法抛出异常,aMapper和bMapper数据库操作皆回滚。 ##### 原因分析 testA方法添加@Transactional注解,testB方法没添加@Transactional注解,两方法处理同一事务中,所以当方法中(不管是testA方法还是testB方法)出现未被处理的异常,数据库操作都会回滚。 ##### 场景2 ```java @Service @Slf4j public class A{ @Autowired private AMapper aMapper; @Autowired private B b; @Transactional public void testA(){ aMapper.update(1,'A'); try { // 处理掉异常 b.testB(); }catch (Exception e){ log.error("exception", e); } } } ``` ```java @Service public class B{ @Autowired private BMapper bMapper; public void testB(){ bMapper.update(1, 'B'); // 制造异常 int x = 1 / 0; } } ``` ##### 测试结果 testA方法单元测试结果:aMapper和bMapper数据库操作皆成功。 ##### 原因分析 testA方法添加@Transactional注解,testB方法没添加@Transactional注解,两方法处理同一事务中,将方法的异常也捕获处理后,数据库操作成功。 #### (3)调用者无@Transactional注解,被调用者有@Transactional注解 ##### 场景1 ```java @Service @Slf4j public class A{ @Autowired private AMapper aMapper; @Autowired private B b; public void testA(){ aMapper.update(1,'A'); // 不处理异常 b.testB(); } } ``` ```java @Service public class B{ @Autowired private BMapper bMapper; @Transactional public void testB(){ bMapper.update(1, 'B'); // 制造异常 int x = 1 / 0; } } ``` ##### 测试结果 testA方法单元测试结果:方法抛出异常,aMapper数据库操作成功,bMapper数据库操作回滚。 ##### 原因分析 testA方法无添加@Transactional注解,testB方法添加@Transactional注解,那么testA使用的是无事务的sqlSession方式来执行,这个执行会被自动提交,数据库操作成功;而testB方法使用事务的方式执行,因为它内部发生异常,所以数据库操作回滚。 ##### 场景2 ```java @Service @Slf4j public class A{ @Autowired private AMapper aMapper; @Autowired private B b; public void testA(){ aMapper.update(1,'A'); b.testB(); // 制造异常 int x = 1 / 0; } } ``` ```java @Service public class B{ @Autowired private BMapper bMapper; @Transactional public void testB(){ bMapper.update(1, 'B'); } } ``` ##### 测试结果 testA方法单元测试结果:方法抛出异常,但aMapper和bMapper数据库操作皆成功。 ##### 原因分析 testA方法无添加@Transactional注解,testB方法添加@Transactional注解,那么testA使用的是无事务的sqlSession方式来执行,这个执行会被自动提交,数据库操作成功;而testB方法使用事务的方式执行,因为它内部并无发生异常,所以数据库操作成功。 #### (4)皆有@Transactional注解 ##### 场景1 ```java @Service public class A{ @Autowired private AMapper aMapper; @Autowired private B b; @Transactional public void testA(){ aMapper.update(1,'A'); b.testB(); } } ``` ```java @Service public class B{ @Autowired private BMapper bMapper; @Transactional public void testB(){ bMapper.update(1, 'B'); // 制造异常 int x = 1 / 0; } } ``` ##### 测试结果 testA方法单元测试结果:方法抛出异常,aMapper和bMapper数据库操作皆回滚。 ##### 原因分析 testA和testB方法都添加@Transactional注解,那么testB方法会使用testA方法的事务,即两方法是在同一事务中,所以只要两方法中任意一方法出现未捕获的异常,数据库操作都会回滚。 #### ##### 场景2 ```java @Service public class A{ @Autowired private AMapper aMapper; @Autowired private B b; @Transactional public void testA(){ aMapper.update(1,'A'); try { // 处理掉异常 b.testB(); }catch (Exception e){ log.error("exception", e); } } } ``` ```java @Service public class B{ @Autowired private BMapper bMapper; @Transactional public void testB(){ bMapper.update(1, 'B'); // 制造异常 int x = 1 / 0; } } ``` ##### 测试结果 testA方法单元测试结果:aMapper和bMapper数据库操作皆回滚。 ##### 原因分析 testA和testB都要求原子操作,所以它们中任意一个发生未捕获异常都会引起事务回滚,而两方法使用同一个事务,所以它们操作都会回滚。 #### (5)调用者有@Transactional注解,被调用者有@Transactional(propagation = Propagation.REQUIRES_NEW)注解 ##### 场景1 ```java @Service public class A{ @Autowired private AMapper aMapper; @Autowired private B b; @Transactional public void testA(){ aMapper.update(1,'A'); try { // 处理掉异常 b.testB(); }catch (Exception e){ log.error("exception", e); } } } ``` ```java @Service public class B{ @Autowired private BMapper bMapper; @Transactional(propagation = Propagation.REQUIRES_NEW) public void testB(){ bMapper.update(1, 'B'); // 制造异常 int x = 1 / 0; } } ``` ##### 测试结果 testA方法单元测试结果:aMapper数据库操作成功,bMapper数据库操作回滚。 ##### 原因分析 testA方法添加@Transactional注解,testB方法添加@Transactional(propagation = Propagation.REQUIRES_NEW)注解,那么testA和testB两个方法都要求使用事务处理,但testB方法是新开一个事务,它执行成功与否对testA方法的事务不影响,所以当testB方法发生异常时,它的数据库操作会回滚,而testA方法没有出现异常,所以它的数据库操作成功。 #### ##### 场景2 ```java @Service public class A{ @Autowired private AMapper aMapper; @Transactional public void testA(){ aMapper.update(1,'A'); try { // 处理掉异常 testAA(); }catch (Exception e){ log.error("exception", e); } } @Transactional(propagation = Propagation.REQUIRES_NEW) public void testAA(){ aMapper.update(2,'AA'); // 制造异常 int x = 1 / 0; } } ``` ##### 测试结果 testA方法单元测试结果:aMapper在两个方法操作数据库都成功。 ##### 原因分析 testA方法添加@Transactional注解,testAA方法添加@Transactional(propagation = Propagation.REQUIRES_NEW)注解,当testA方法调用testAA方法时,调用的是A对象的方法,而spring对添加事务注解的操作,不是直接使用用户定义的类对象,而是由spring生成的动态代理对象来执行事务操作,所以当我们在同一个类里面直接调用的时候,并被调用方法没有使用到注解功能,而是作为简单的对象方法处理。所以这两方法是属于同一事务里面,异常也被捕获,所以两方法数据库操作皆成功。 #### ##### 场景3 ```java @Service public class A{ @Autowired private AMapper aMapper; @Transactional public void testA(){ aMapper.update(1,'A'); // 获取本对象的动态代理对象 A a = (A) AopContext.currentProxy(); try { // 处理掉异常 a.testAA(); }catch (Exception e){ log.error("exception", e); } } @Transactional(propagation = Propagation.REQUIRES_NEW) public void testAA(){ aMapper.update(2,'AA'); // 制造异常 int x = 1 / 0; } } ``` ##### 测试结果 testA方法单元测试结果:testA方法中的aMapper数据库操作成功,testAA方法中的aMapper数据库操作回滚。 ##### 原因分析 上面示例是正确使用本对象的事务注解属性的方式,要先拿到代理对象,然后再调用,才能真正使用到事务注解的方法,即这里会在testAA方法中使用新的事务,它的操作结果对方法testA的事务不影响。 #### 重点强调 是否开启事务,我们可以从日志里看出来,如果日志里出现如Transaction synchronization suspending SqlSession,挂起当前事务;再新开事务:Registering transaction synchronization for SqlSession,这样就是新开了事务来执行被调用的方法,很多信息我们是可以从日志里看出来的。事务执行完都会有commit日志,出错回滚会有roll-back日志,所以调试代码是执行的事务是不是自己想要的,一定要看日志,切记,切记,切记。