知识点:
@Transactional
是一种基于注解管理事务的方式,spring通过动态代理的方式为目标方法实现事务管理的增强。
@Transactional 实质是使用了 JDBC 的事务来进行事务控制的
@Transactional 基于 Spring 的动态代理的机制
@Transactional 实现原理:
1) 事务开始时,通过AOP机制,生成一个代理connection对象,
并将其放入 DataSource 实例的某个与 DataSourceTransactionManager 相关的某处容器中。 在接下来的整个事务中,客户代码都应该使用该 connection 连接数据库,执行所有数据库命令。
[不使用该 connection 连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚]
(物理连接 connection 逻辑上新建一个会话session;
DataSource 与 TransactionManager 配置相同的数据源)
2) 事务结束时,回滚在第1步骤中得到的代理 connection 对象上执行的数据库命令,然后关闭该代理 connection 对象。(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)
使用注意
1)接口实现类或接口实现方法上,而不是接口类中。
2)访问权限:public 的方法才起作用。@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。
系统设计:将标签放置在需要进行事务管理的方法上,而不是放在所有接口实现类上:
3)@Transactional注解在外部调用的函数上才有效果,内部调用的函数添加无效,要切记。这是由AOP的特性决定的。
函数之间调用
业务:创建法律模板,添加关联法律模板关系
单个类之间函数调用:外部加
情形1: create添加了@Transactional注解,insertLawInnerMappingBatch函数没有添加。insertLawInnerMappingBatch抛异常
@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){this.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}// 插入关联关系public void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行有异常!");
// lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);}
单元测试:
@Autowiredprivate LawTemplateService lawTemplateService;@Testpublic void createRelLaw() {LawTemplate law = new LawTemplate();law.setLawName("成年人");law.setLawContent("成年人为完全民事行为能力人,可以独立实施民事法律行为");law.setLawCase("成年人行为xxx");law.setCreateStaffId(1L);law.setCreateTime(new Date());List<Long> lawIds = new ArrayList<>();lawIds.add(1L);law.setRelLawIds(lawIds);lawTemplateService.create(law);}
执行报错,两个数据的操作都会回滚
单个类之间函数调用:都加
情形2: create和insertLawInnerMappingBatch都添加了@Transactional注解。insertLawInnerMappingBatch抛异常
@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){this.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行有异常!");
// lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);}
结果:同第一种情况一样,两个函数对数据库操作都会回滚。因为同一个类中函数相互调用的时候,内部函数添加@Transactional注解无效。@Transactional注解只有外部调用才有效。
单个类之间函数调用:内部加
情形3: create不加了@Transactional注解,insertLawInnerMappingBatch函数加@Transactional注解。insertLawInnerMappingBatch抛异常
public Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){this.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行有异常!");
// lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);}
结果:两个函数对数据库的操作都不会回滚。因为内部函数@Transactional注解添加和没添加一样。
单个类之间函数调用:外部加,内部捕获异常
情形4: create加了@Transactional注解,insertLawInnerMappingBatch加@Transactional注解。create 内部添加插入关系的捕获, insertLawInnerMappingBatch抛异常
@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){try {this.insertLawInnerMappingBatch(lawTemplate);}catch (Exception e){e.printStackTrace();}}return lawTemplate.getLawId();}// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行有异常!");
// lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);}
结果:不管insertLawInnerMappingBatch操作是否成功,insertSelective没有发生异常就会成功。事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。
不同类之间函数调用
不同类之间函数调用:外部加
情形1: create添加了@Transactional注解,insertLawInnerMappingBatch函数没有添加。insertLawInnerMappingBatch抛异常
@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Resourceprivate ReplyTemplateService replyTemplateService;@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}
}@Slf4j
@Service
public class ReplyTemplateService {// 插入关联关系public void insertLawInnerMappingBatch(LawTemplate lawTemplate) {throw new RuntimeException("模拟函数执行有异常!");}}
单元测试:
@Autowiredprivate LawTemplateService lawTemplateService;@Testpublic void createRelLaw() {LawTemplate law = new LawTemplate();law.setLawName("成年人");law.setLawContent("成年人为完全民事行为能力人,可以独立实施民事法律行为");law.setLawCase("成年人行为xxx");law.setCreateStaffId(1L);law.setCreateTime(new Date());List<Long> lawIds = new ArrayList<>();lawIds.add(1L);law.setRelLawIds(lawIds);lawTemplateService.create(law);}
执行报错,两个数据的操作都会回滚
不同类之间函数调用:都加
情形2: create和insertLawInnerMappingBatch都添加了@Transactional注解。insertLawInnerMappingBatch抛异常
@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Resourceprivate ReplyTemplateService replyTemplateService;@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}
}@Slf4j
@Service
public class ReplyTemplateService {// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate) {throw new RuntimeException("模拟函数执行有异常!");}}
结果:两个函数对数据库的操作都回滚了。两个函数里面用的还是同一个事务。
不同类之间函数调用:引用加
情形3: create不加了@Transactional注解,insertLawInnerMappingBatch函数加@Transactional注解。insertLawInnerMappingBatch抛异常
@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Resourceprivate ReplyTemplateService replyTemplateService;public Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}
}@Slf4j
@Service
public class ReplyTemplateService {// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate) {throw new RuntimeException("模拟函数执行有异常!");}}
结果:create里面insertSelective正常不报错,会插入。不管 LawTemplateService 里面 insertLawInnerMappingBatch 是否报错。因为两个是不同事物的
不同类之间函数调用:外部加,内部捕获异常
情形4: create加了@Transactional注解,insertLawInnerMappingBatch加@Transactional注解。create 内部添加插入关系的捕获, insertLawInnerMappingBatch抛异常
@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Resourceprivate ReplyTemplateService replyTemplateService;@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){try {replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}catch (Exception e){e.printStackTrace();}}return lawTemplate.getLawId();}}@Slf4j
@Service
public class ReplyTemplateService {// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate) {throw new RuntimeException("模拟函数执行有异常!");}}
部分异常:
Transaction rolled back because it has been marked as rollback-only
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-onlyat org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:870)at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:707)at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654)at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407)at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
结果:create操作失败,而且还添加了抛出异常:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。两个函数用的是同一个事务。insertLawInnerMappingBatch函数抛了异常,调了事务的rollback函数。事务被标记了只能rollback了。程序继续执行,create函数里面把异常给抓出来了,这个时候create函数没有抛出异常,既然你没有异常那事务就需要提交,会调事务的commit函数。而之前已经标记了事务只能rollback-only(以为是同一个事务)。直接就抛异常了,不让继续调用了。
有@Transactional的函数里面调用有@Transactional的函数的时候,进入第二个函数的时候是新的事务,如果还是沿用之前的事务。如果报错就会抛UnexpectedRollbackException异常。
不同类之间函数调用:引用添加 Propagation.REQUIRES_NEW
情形5: create加了@Transactional注解,insertLawInnerMappingBatch加@Transactional注解。create 内部添加插入关系的捕获, insertLawInnerMappingBatch抛异常,添加 Propagation.REQUIRES_NEW
@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Resourceprivate ReplyTemplateService replyTemplateService;@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){try {replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}catch (Exception e){e.printStackTrace();}}return lawTemplate.getLawId();}}@Slf4j
@Service
public class ReplyTemplateService {// 插入关联关系@Transactional(propagation = Propagation.REQUIRES_NEW)public void insertLawInnerMappingBatch(LawTemplate lawTemplate) {throw new RuntimeException("模拟函数执行有异常!");}}
结果: 不管insertLawInnerMappingBatch操作是否成功,insertSelective没有发生异常就会成功。因为两个函数不是同一个事务了。
常见出错的地方
总结以上需要注意@Transactional失效的场景:
1、异常被捕获后没有抛出
@Transactionalpublic Long create(LawTemplate lawTemplate) {try {lawTemplateMapper.insertSelective(lawTemplate);return lawTemplate.getLawId();}catch (Exception e){e.printStackTrace();}return null;}
这个很好理解,事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。
2、抛出非运行时异常 得是RuntimeException 或者指定异常
异步虽然抛出了,但是抛出的是非RuntimeException
类型的异常(抛出的异常要继承RuntimeException才有效),依旧不会生效。
@Transactionalpublic Long create(LawTemplate lawTemplate) throws MyException {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){try {replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}catch (Exception e){throw new MyException ();}}return lawTemplate.getLawId();}
如果指定了回滚异常类型为Exception,那么就可以回滚非RuntimeException
类型异常了。
@Transactional(rollbackFor = Exception.class)
3、方法内部直接调用 -- 这个容易忽略,需要去注意!
如果先调用createOne(),那么create()执行成功是不会回滚的,其原因就是@Transactional根本没生成代理,如果直接调用create() ,如果报错会回滚。
@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Autowiredprivate LawTemplateService lawTemplateService;public void createOne(LawTemplate lawTemplate) {this.create(lawTemplate);}@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){this.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}// 插入关联关系public void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行插入关系失败后抛出异常!");}
}
insertSelective 插入成功, insertLawInnerMappingBatch报错,并没有回滚。为啥呢? 因为createOne里面调用create没有生成代理。
修改方式,把当前类自己注入一下调用
@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;public void createOne(LawTemplate lawTemplate) {SpringUtils.getBean(LawTemplateService.class).create(lawTemplate);}@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){this.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}// 插入关联关系public void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行插入关系失败后抛出异常!");}
}
insertSelective 插入成功, insertLawInnerMappingBatch报错,会回滚。
4、注解到private方法上
idea直接会给出提示Methods annotated with ‘@Transactional’ must be overridable
,原理很简单,private修饰的方式,spring无法生成动态代理。直接报错了
总结:
@Transactional 使用:1, 要注意抛出异常是否是运行时异常;2,注解在外部调用的函数上才有效果;3,内部调用的,要注意是否生成代理。