@Transactional 学习和使用

news/2025/2/19 2:50:13/

知识点:

@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,内部调用的,要注意是否生成代理。


http://www.ppmy.cn/news/1009359.html

相关文章

【C#学习笔记】数组和索引器

文章目录 数组单维数组多维数组交错数组 索引器类上的索引器方法1方法2 接口中的索引器 数组 数组具有以下属性&#xff1a; 数组可以是一维、多维或交错的。创建数组实例时&#xff0c;将建立纬度数量和每个纬度的长度。 这些值在实例的生存期内无法更改。数值数组元素的默认…

英语写作中“遭受”、“面对”suffer (from)、expose、encounter、face、confront等的用法

一、在写作中涉及所“遭受”、所“面对”的是不利的因素、问题和结果&#xff0c;例如&#xff1a; difficulty、issue、problem、change、danger、disaster、consequence、(negative) result、overflow、congestion、insecurity、attack、disorder、disaster、obstacle 、thr…

CDN安全面临的问题及防御架构

CDN安全 SQL注入攻击&#xff08;各开发小组针对密码和权限的管理&#xff0c;和云安全部门的漏洞扫描和渗透测试&#xff09; Web Server的安全&#xff08;运营商和云安全部门或者漏洞纰漏第三方定期发布漏洞报告修复&#xff0c;例如&#xff1a;nginx版本号和nginx resol…

【Linux】【docker】安装sonarQube免费社区版9.9

文章目录 ⛺sonarQube 镜像容器⛺Linux 安装镜像&#x1f341;出现 Permission denied的异常&#x1f341;安装sonarQube 中文包&#x1f341;重启服务 ⛺代码上传到sonarQube扫描&#x1f341;java语言配置&#x1f341;配置 JS TS Php Go Python⛏️出现异常sonar-scanner.ba…

HTML5中Canvas学习笔记:Canvas

目录 一、HTML中Canvas画图strokeStyle 和 fillStyle 的区别是什么&#xff1f; 二、如何设置一幅canvas图中某个颜色透明&#xff1f; 三、H5 canvas中strokeRect参数如果是小数&#xff0c;如何处理&#xff1f; 四、H5 Canvas中如何画圆角矩形框&#xff1f; 一、HTML中…

TextBox基本使用

作用&#xff1a; 文本框&#xff0c;用于展示文本、输入文本 常用属性&#xff1a; 文本属性 允许多行 常用事件&#xff1a; 后台代码&#xff1a; private void textBox4_TextChanged(object sender, EventArgs e){//实时获取输入的文本label3.Text textBox4.Text;}

2023-08-05——JVM 栈

栈 stack 栈&#xff1a;数据结构 程序数据结构算法 栈&#xff1a;先进后出&#xff0c;后进先出 好比一个&#xff1a;桶 队列&#xff1a;先进先出&#xff08;FIFO &#xff1a;First Input First Out&#xff09; 好比一个&#xff1a;管道 栈&#xff1a;喝多了吐。队列…

Implicit Graph Neural Networks: A Monotone Operator Viewpoint

Implicit graph neural networks (IGNNs) solve a fixed-point equilibrium equation using Picard iteration for representation learning. 通常G的选择为&#xff1a;