Spring如何使用自定义注解来实现自动管理事务?

人可以做他(她)想做的,但不能要他(她)想要的

一个目录

  • 前言
  • 业务代码展示
  • 手动挡
  • 自动挡
  • 事务失效的问题
  • 代码地址

前言

在两年半以前,我写了一篇博客:框架的灵魂之注解基础篇:
在这里插入图片描述
在那篇博客的结尾,我埋了一个坑:
在这里插入图片描述
如今,我练习时长达两年半,终于摔锅归来!
在这里插入图片描述

本篇博客基于SpringBoot整合MyBatis-plus,如果有不懂这个的,
可以查看我的这篇博客:快速CRUD的秘诀之SpringBoot整合MyBatis-Plus

注意:这篇博客的重点在于:如何利用自定义注解结合Spring的AOP思想来实现自动进行事务管理

所以事务方面的知识可以说是一点也没有,需要这方面知识的可以划走了hhh
在这里插入图片描述

业务代码展示

0.老规矩,首先来配置一下配置文件application.yml

# 配置数据源
spring:datasource:# 数据库路径jdbc:mysql://localhost:3306/mydb 的缩写,并配置时区url: jdbc:mysql:///mydb?serverTimezone=GMT%2B8username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver # 3.2.0开始支持SPI可省略此配置# 打印MyBatis SQL 日志
logging:level:com.guqueyue.myTransactional.dao: debug # 写接口的包名server:port: 8082 #端口

1.控制层

package com.guqueyue.myTransactional.controller;import com.guqueyue.myTransactional.entity.User;
import com.guqueyue.myTransactional.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @Author: guqueyue* @Description: 用户控制层* @Date: 2023/12/19**/
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate IUserService userService;/*** 插入用户* @return*/@RequestMapping("/insertUser")public Integer insertUser(User user) {System.out.println("接收到的用户为:" + user);return userService.insertUser(user);}
}

2.service接口

package com.guqueyue.myTransactional.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.guqueyue.myTransactional.entity.User;/*** @Author: guqueyue* @Description: 用户service接口* @Date: 2023/12/19**/
public interface IUserService extends IService<User> {Integer insertUser(User user);
}

3.service实现类

package com.guqueyue.myTransactional.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.guqueyue.myTransactional.dao.UserMapper;
import com.guqueyue.myTransactional.entity.User;
import com.guqueyue.myTransactional.service.IUserService;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** @Author: guqueyue* @Description: 用户实现类* @Date: 2023/12/19**/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Resourceprivate UserMapper userMapper;@Overridepublic Integer insertUser(User user) {int result = userMapper.insert(user);// 写一个异常int i = 1/0;return result;}
}

4.持久层

package com.guqueyue.myTransactional.dao;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.guqueyue.myTransactional.entity.User;/*** @Author: guqueyue* @Description: 映射接口UserMapper* @Date: 2023/12/19**/
public interface UserMapper extends BaseMapper<User> {}

我们这个时候启动项目,浏览器输入:http://localhost:8082/user/insertUser?username=张三&password=666666

我们在控制台可以看到SQL语句的执行,并且随后抛出了一个异常:
在这里插入图片描述
在这里插入图片描述
观察数据库发现,成功插入了一条数据:
在这里插入图片描述
但是这样肯定是不对的,如果一个方法发生了异常,一部分代码执行一部分代码没执行,这种情况是很危险的!!!

所以,我们需要回滚这个方法已经执行的SQL,不然就全乱套啦!

因此,我们需要事务管理来进行SQL的回滚。

那么,怎么实现呢?请看下文
在这里插入图片描述

手动挡

我们先来手动实现一下事务管理吧!

1.编写工具方法类MyTransactionalUtil便于处理事务:

package com.guqueyue.myTransactional.util;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;/*** @Author: guqueyue* @Description: 自定义事务工具类* @Date: 2023/12/28**/
@Component
public class MyTransactionalUtil {@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;/*** @Description 开启事务* @Param []* @return org.springframework.transaction.TransactionStatus**/public TransactionStatus begin() {return dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());}/*** @Description 提交事务* @Param [transactionStatus]* @return void**/public void commit(TransactionStatus transactionStatus) {if (transactionStatus != null) {dataSourceTransactionManager.commit(transactionStatus);}}/*** @Description 回滚事务* @Param [transactionStatus]* @return void**/public void rollback(TransactionStatus transactionStatus) {if (transactionStatus != null) {dataSourceTransactionManager.rollback(transactionStatus);}}
}

2.这样我们就可以直接在service实现类中注入MyTransactionalUtil来实现事务管理啦

package com.guqueyue.myTransactional.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.guqueyue.myTransactional.dao.UserMapper;
import com.guqueyue.myTransactional.entity.User;
import com.guqueyue.myTransactional.service.IUserService;
import com.guqueyue.myTransactional.util.MyTransactionalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;import javax.annotation.Resource;/*** @Author: guqueyue* @Description: 用户实现类* @Date: 2023/12/19**/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate MyTransactionalUtil myTransactionalUtil;@Resourceprivate UserMapper userMapper;@Overridepublic Integer insertUser(User user) {int result = 0;TransactionStatus begin = null;try {// 开启事务begin = myTransactionalUtil.begin();result = userMapper.insert(user);// 写一个异常int i = 1/0;// 提交事务myTransactionalUtil.commit(begin);}catch (Exception e) {e.printStackTrace();// 回滚事务myTransactionalUtil.rollback(begin);//        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 手动回滚return 0;}return result;}
}

重新启动项目,浏览器输入:http://localhost:8082/user/insertUser?username=李四&password=7777777

同样,观察控制台,我们可以看到SQL语句执行以及异常抛出:
在这里插入图片描述
在这里插入图片描述
但是刷新并观察数据库,我们并没有发现数据插入:

在这里插入图片描述
说明,我们的事务管理起了作用。

那么,看到这里,你可能发现并没有用到我文章开头说的自定义注解、AOP思想

在这里插入图片描述

我知道你很急,但你先别急,在下面 ↓↓↓

自动挡

在上一章节中,我们实现了事务管理,但是非常的麻烦,每一个方法都得如法炮制一遍!

但是,我们可以很明显的发现,其实这些代码都是一样的,那么有没有简便方法呢?

当然有!不过这个世间是平衡的,这个地方少了,那么其他地方就得多。

在这里插入图片描述
1.首先,我们创建一个自定义注解:

package com.guqueyue.myTransactional.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @Author: guqueyue* @Description: 自定义注解实现事务* @Date: 2023/12/28**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTransactional {}

关于自定义注解不懂的可以看我的这篇博客:框架的灵魂之注解基础篇

2.上AOP

package com.guqueyue.myTransactional.aop;import com.guqueyue.myTransactional.util.MyTransactionalUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.TransactionStatus;/*** @Author: guaueyue* @Description: 通过拦截自定义注解实现事务* @Date: 2023/12/28**/
@Slf4j
@Aspect
@Component
public class MyTransactionAspect {@Autowiredprivate MyTransactionalUtil transactionalUtil;/*** @Description 通过拦截自定义注解@MyTransactional来实现事务* 				@annotation()里为自定义注解的路径* @Param [joinPoint]* @return java.lang.Object**/@Around(value = "@annotation(com.guqueyue.myTransactional.annotation.MyTransactional)") public Object around(ProceedingJoinPoint joinPoint) {Object result = null;TransactionStatus begin = null;try {// 开启事务begin = transactionalUtil.begin();// 执行目标方法result = joinPoint.proceed();// 提交事务transactionalUtil.commit(begin);} catch (Throwable e) {transactionalUtil.rollback(begin);log.info("事务回滚...");e.printStackTrace();}return result;}
}

这里的joinPoint.proceed()方法就是执行需要事务管理方法的意思,

我们可以很明显的看出跟上文手动管理事务的逻辑是一样的,只不过抽象出来了

// 执行目标方法
Object proceed = joinPoint.proceed();

3.使用

 	@MyTransactional@Overridepublic Integer insertUser(User user) {int result = userMapper.insert(user);// 写一个异常int i = 1/0;return result;}

这下我们只需要在需要事务管理的方法上面加一个自定义注解就可以实现功能了,是不是很方便优雅?
在这里插入图片描述
4.效果

同样的,重新启动项目,浏览器输入:http://localhost:8082/user/insertUser?username=王五&password=888888

同样,观察控制台,我们可以看到SQL语句执行以及异常抛出:
在这里插入图片描述
在这里插入图片描述
但是刷新数据库,发现什么也没有发生!
在这里插入图片描述
说明功能实现啦!

4.原理

那么,这个功能是怎么实现的呢?

其实很简单,Spring会拦截使用了自定义注解@MyTransactional的方法,进行环绕增强。

这样方法就会变成类似于下面的效果(以下是伪代码无法执行)

	@Autowiredprivate MyTransactionalUtil transactionalUtil;/*** @Description 通过拦截自定义注解@MyTransactional来实现事务* @Param [joinPoint]* @return java.lang.Object**/@Around(value = "@annotation(com.guqueyue.myTransactional.annotation.MyTransactional)")public Object around(ProceedingJoinPoint joinPoint) {Object result = null;TransactionStatus begin = null;try {// 开启事务begin = transactionalUtil.begin();// 执行目标方法@Overridepublic int insertUser(User user) {int result = userMapper.insert(user);// 写一个异常int i = 1/0;return result;}// 提交事务transactionalUtil.commit(begin);} catch (Throwable e) {transactionalUtil.rollback(begin);log.info("事务回滚...");e.printStackTrace();}return result;}

(以上是伪代码无法执行)

5.后话

当然了,这个功能其实Spring框架已经实现了,大家用官方提供的@Transactional注解就好了,这里也只是给大家讲解一下原理:

	@Transactional@Overridepublic Integer insertUser(User user) {int result = userMapper.insert(user);// 写一个异常int i = 1/0;return result;}

文章篇幅有限,就不具体验证截图示意了。

事务失效的问题

大家难免在编写代码的过程中遇到又需要事务管理,又需要异常处理的问题,如代码里面使用了文件流

然后有些新手小伙伴可能代码就变成了:

 	@Transactional@Overridepublic Integer insertUser(User user) {int result = 0;try {result = userMapper.insert(user);// 写一个异常FileInputStream fileInputStream = new FileInputStream("");}catch (Exception e) {e.printStackTrace();}return result;}

重启项目,浏览器输入:http://localhost:8082/user/insertUser?username=赵六&password=999999

控制台可以看到SQL语句执行以及异常抛出:
在这里插入图片描述
在这里插入图片描述

但是刷新查看数据库发现插入了一条数据,说明事务失效了:
在这里插入图片描述
这个是为什么呢?

这里我就不卖关子了,因为如果你这样写的话,就成类似于这样了:
(以下是伪代码无法执行)

	@Autowiredprivate MyTransactionalUtil transactionalUtil;/*** @Description 通过拦截自定义注解@MyTransactional来实现事务* @Param [joinPoint]* @return java.lang.Object**/@Around(value = "@annotation(com.guqueyue.myTransactional.annotation.MyTransactional)")public Object around(ProceedingJoinPoint joinPoint) {Object result = null;TransactionStatus begin = null;try {// 开启事务begin = transactionalUtil.begin();// 执行目标方法@Overridepublic int insertUser(User user) {int result = 0;try {result = userMapper.insert(user);// 写一个异常FileInputStream fileInputStream = new FileInputStream("");}catch (Exception e) {e.printStackTrace();}return result;}// 提交事务transactionalUtil.commit(begin);} catch (Throwable e) {transactionalUtil.rollback(begin);log.info("事务回滚...");e.printStackTrace();}return result;}

(以上是伪代码无法执行)
在这里插入图片描述

我们可以很轻松的发现,方法内部有一个 try…catch ,所以外部的 try…catch 就失效了。

那么,怎么办呢?很简单,向外部抛出异常就好了,记得一直抛到控制层,不然代码会报错哦:

	@Transactional@Overridepublic Integer insertUser(User user) throws Exception{int result = userMapper.insert(user);// 写一个异常FileInputStream fileInputStream = new FileInputStream("");return result;}

至于效果,留有读者自行验证了。

其实不用验证了,上面代码肯定是无效的啦,

因为Spring的默认的事务规则是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚。

而上文中的java.io.FileNotFoundException异常属于IO异常(IOException

我们需要指定一下异常类型:

 	@Transactional(rollbackFor = Exception.class)@Overridepublic Integer insertUser(User user) throws Exception {int result = userMapper.insert(user);// 写一个异常FileInputStream fileInputStream = new FileInputStream("");return result;}

代码地址

本文代码已开源:

git clone https://gitee.com/guqueyue/my-blog-demo.git

请切换到gitee分支,然后查看myTransactional模块即可!

这戛然而止的结尾。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/645085.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA 学习 面试(八)集合类

集合类 集合&#xff08;Collection&#xff09; 1、 List列表 &#xff1a; 有序 可重复 1、ArrayList : 数组列表 &#xff0c;内部是通过Array实现&#xff0c;对数据列表进行插入、删除操作时都需要对数组进行拷贝并重排序&#xff0c;因此在知道存储数据量时&#xff0c…

【GAMES101】Lecture 09 重心坐标

我们之前说着色过程中以及这个计算法线的时候需要用到这个插值&#xff08;Interpolation&#xff09;&#xff0c;然后插值是通过这个重心坐标&#xff08;Barycentric Coordinates&#xff09;来实现的 目录 重心坐标 插值 重心坐标 注意哈我们这里说的三角形的重心坐标并…

RK3399平台开发系列讲解(USB篇)BusHound 工具使用介绍

🚀返回专栏总目录 文章目录 一、BusHound简介二、BusHound的下载三、BusHound设备窗口四、BUSHound发送命令窗口沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 BusHound软件是由美国perisoft公司研制的一种专用于PC机各种总线数据包监视和控制的开发工具软件,其名…

【GitHub项目推荐--一款美观的开源社区系统】【转载】

推荐一款开源社区系统&#xff0c;该系统基于主流的 Java Web 技术栈&#xff0c;如果你是一名 Java 新手掌握了基本 JavaEE 框架知识&#xff0c;可以拿本项目作为练手项目。 开源社区系统功能还算完善包含发布帖子、发布评论、私信、系统通知、点赞、关注、搜索、用户设置、…

What is `Filter` does?

过滤器&#xff08;Filter&#xff09;是Java Servlet规范中的一部分&#xff0c;它提供了一种在请求到达目标资源之前或响应发送给客户端之前进行预处理和后处理的能力。 通过编写自定义的过滤器类并将其注册到Web应用程序中&#xff0c;开发者可以实现诸如登录验证、权限控制…

边缘计算及相关产品历史发展

边缘计算及相关产品历史发展 背景边缘计算的历史CDN&#xff08;Content Delivery Network&#xff09;Cloudlet雾计算MEC&#xff08;Multi-Access Edge Computing&#xff0c;MEC&#xff09; 边缘计算的现状云计算厂商硬件厂商软件基金会 背景 最近&#xff0c;公司部分业务…

RT-DETR优化改进:IoU系列篇 | Focaler-IoU​​​​​​​更加聚焦的IoU损失Focaler-IoU |2024年最新发表

🚀🚀🚀本文改进:Focaler-IoU更加聚焦的IoU损失Focaler-IoU,能够在不同的检测任务中聚焦不同的回归样本,使用线性区间映射的方法来重构IoU损失 🚀🚀🚀RT-DETR改进创新专栏:http://t.csdnimg.cn/vuQTz 🚀🚀🚀学姐带你学习YOLOv8,从入门到创新,轻轻松松搞…

Redis中BigKey的分析与优化

Redis中BigKey的分析与优化 Redis以其出色的性能和易用性&#xff0c;在互联网技术栈中占据了重要的地位。 但是&#xff0c;高效的工具使用不当也会成为性能瓶颈。在Redis中&#xff0c;BigKey是常见的性能杀手之一&#xff0c;它们会消耗过多的内存&#xff0c;导致网络拥塞…

【每日一题】最大交换

文章目录 Tag题目来源解题思路方法一&#xff1a;暴力法方法二&#xff1a;贪心 写在最后 Tag 【暴力法】【贪心法】【数组】【2024-01-22】 题目来源 670. 最大交换 解题思路 本题的数据规模比较小&#xff0c;暴力法也可以通过。以下将会介绍暴力法和本题最优法。 方法一…

14027.ptp 控制流

文章目录 1 ptp 控制流1.1 控制流分层 1 ptp 控制流 1.1 控制流分层 大体分为4层&#xff1a;1 ptp4l层&#xff1a; 获取配置文件、创建时钟、poll监控文件描述符。2 clock时钟层&#xff1a;提供提供clock_poll、clock_create、clock_sync 等3 port 端口层&#xff1a;port…

通过 GScan 工具自动排查后门

一、简介 GScan 是一款为安全应急响应提供便利的工具&#xff0c;自动化监测系统中常见位置。 工具运行环境&#xff1a;CentOS (6、7) python (2.x、3.x) 工具检查项目&#xff1a; 1、主机信息获取 2、系统初始化 alias 检查 3、文件类安全扫描 3.1、系统重要文件完整行…

JFinal项目搭建

JFinal项目搭建 JFinal项目搭建 JFinal项目搭建 首先创建maven项目&#xff1a; 删掉报错的jsp页面&#xff1a; 在pom.xml中加入坐标&#xff1a; <dependency> <groupId>com.jfinal</groupId> <artifactId>jfinal-undertow</artifactId>…

零基础学习【Mybatis Plus】这一篇就够了

学习目录 1. 快速入门1-1. 常用注解总结 1-2. 常用配置 2. 核心功能3. 扩展功能4. 插件功能 1. 快速入门 1-1. 常用注解 MybatisPlus中比较常用的几个注解如下&#xff1a; TableName: 用来指定表名Tableld: 用来指定表中的主键字段信息TableField: 用来指定表中的普通字段信…

基于openssl v3搭建ssl安全加固的c++ tcpserver

1 概述 tcp server和tcp client同时使用openssl库&#xff0c;可对通信双方流通的字节序列进行加解密&#xff0c;保障通信的安全。本文以c编写的tcp server和tcp client为例子&#xff0c;openssl的版本为v3。 2 安装openssl v3 2.1 安装 perl-IPC-Cmd openssl项目中的co…

AR 自回归模型

文章目录 总的代码ADF 检验(是否平稳)差分操作拟合AR 模型预测可视化总的代码 import pandas as pd import numpy as np import matplotlib.pyplot as plt from statsmodels.tsa.ar_model import AutoReg from statsmodels.tsa.stattools import adfuller# 生成一个示例时间序…

在人工智能时代,如何利用AI达到行业领先地位?

人工智能很快将成为企业开展业务的一个必要环节。各企业都会具备AI战略&#xff0c;就像其具有社交媒体战略、品牌战略和人才战略等一样。 因此&#xff0c;如果企业希望在竞争中脱颖而出、获得优势&#xff0c;不能只是使用AI&#xff0c;而是要以AI为先导&#xff0c;创造行业…

基于springboot+vue的海滨体育馆管理系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 研究背景…

邮件服务支持Exchange协议,资产历史账号支持设置保留数量,JumpServer堡垒机v3.10.2 LTS版本发布

2024年1月22日&#xff0c;JumpServer开源堡垒机正式发布v3.10.2 LTS版本。JumpServer开源项目组将对v3.10 LTS版本提供长期的支持和维护&#xff0c;并定期迭代发布小版本。欢迎广大社区用户升级至v3.10 LTS版本&#xff0c;以获得更佳的使用体验。 在v3.10.2 LTS版本中&…

04 经典的OSPF

思维导图的方式回顾OSPF 什么是OSPF?为什么需要OSPF? - 华为 (huawei.com) 1 ospf 领行学习思维导图 1.1 ospf 的工作过程 建立领据表同步数据库计算路由表1.2 ospf 的状态

用JavaFX写了一个简易的管理系统

文章目录 前言正文一、最终效果1.1 主页面1.2 动物管理页面-初始化1.3 动物管理页面-修改&新增1.4 动物管理页面-删除&批量删除 二、核心代码展示2.1 启动类2.2 数据库配置-db.setting2.3 日志文本域组件2.4 自定义表格视图组件2.5 自定义分页组件2.6 动物管理页面2.7 …