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

news2024/11/15 19:28:34
人可以做他(她)想做的,但不能要他(她)想要的

一个目录

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

前言

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

本篇博客基于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%2B8
    username: root
    password: 123456
    driver-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 {

    @Autowired
    private 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 {

    @Resource
    private UserMapper userMapper;

    @Override
    public 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 {

    @Autowired
    private 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 {

    @Autowired
    private MyTransactionalUtil myTransactionalUtil;

    @Resource
    private UserMapper userMapper;

    @Override
    public 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 {

    @Autowired
    private 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
    @Override
    public 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的方法,进行环绕增强。

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

	@Autowired
    private 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();

            // 执行目标方法
            @Override
            public 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
    @Override
    public Integer insertUser(User user) {

        int result = userMapper.insert(user);
        // 写一个异常
        int i = 1/0;

        return result;
    }

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

事务失效的问题

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

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

 	@Transactional
    @Override
    public 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语句执行以及异常抛出:
在这里插入图片描述
在这里插入图片描述

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

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

	@Autowired
    private 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();

            // 执行目标方法
           @Override
		    public 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
    @Override
    public 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)
    @Override
    public 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.coloradmin.cn/o/1408640.html

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

相关文章

写点东西《检查和更新NPM包》

写点东西《检查和更新NPM包》 检查和更新 NPM 包 TL;DR&#xff1b; 用于检查和更新软件包的 NPM 命令# [](#npm-outdated)npm outdatednpm updatenpm update --save-dev --savenpm update -g npm-check-updates 检查和更新软件包的命令npm install -g npm-check-updatesnpx np…

SQL 系列教程(二)

目录 SQL DELETE 语句 DELETE 语句 演示数据库 DELETE 实例 删除所有行 SQL TOP, LIMIT, ROWNUM 子句 TOP 子句 演示数据库 SQL TOP、LIMIT 和 ROWNUM 示例 SQL TOP PERCENT 实例 添加WHERE子句 SQL MIN() 和 MAX() 函数 MIN() 和 MAX() 函数 演示数据库 MIN() …

ASUS华硕无畏Pro15笔记本电脑(M6500QB,M6500QH)工厂模式原厂OEM预装Windows11.22H2系统 含Recovery恢复

原装出厂Windows11系统适用于华硕无畏15笔记本电脑型号&#xff1a;M6500QB和M6500QH 链接&#xff1a;https://pan.baidu.com/s/1AVGLN6-ILIRogOMj48Mk1w?pwdmi7d 提取码&#xff1a;mi7d 带有ASUS RECOVERY恢复功能、自带所有驱动、出厂主题专用壁纸、系统属性联机支持…

千兆以太网测试仪可以做什么

网络性能测试仪是一种用于测量和监测网络质量的工具。它可以帮助用户评估网络的性能&#xff0c;包括带宽、延迟、丢包率等指标&#xff0c;并及时发现网络故障&#xff0c;以保证网络的高效运行。网络性能测试仪可以应用于多个领域&#xff0c;如网络运营商、企业网络、数据中…

鸿蒙开发案列一

1、开发需求 案例app一打开是“Hello world” 界面&#xff0c;开发者点击“Hello world”变成“Hello ArkUI”’ 2、源代码 Entry Component struct Hello {State person_name: string Worldbuild() {Row() {Column() {Text(Hello this.person_name).fontSize(50).fontWei…

linux安装docker--更具官网教程

1.访问https://docs.docker.com/ 2.进入download 3输入cento 或者直接访问地址Install Docker Engine on CentOS | Docker Docs 4一步一步根据官网命令走 2安装 3 4 方式一&#xff1a; service docker start&#xff08;开启&#xff09; service docker status&#xff08…

《游戏-03_3D-开发》之—新输入系统人物移动攻击连击

本次修改unity的新输入输出系统。本次修改unity需要重启&#xff0c;请先保存项目&#xff0c; 点击加号起名为MyCtrl&#xff0c; 点击加号设置为一轴的&#xff0c; 继续设置W键&#xff0c; 保存 生成自动脚本&#xff0c; 修改MyPlayer代码&#xff1a; using UnityEngine;…

单核QPS近6000S,陌陌基于OceanBase的持久化缓存探索与实践

挚文集团于 2011 年 8 月推出了陌陌&#xff0c;这款立足地理位置服务的开放式移动视频社交应用在中国社交平台领域内独树一帜。陌陌和探探作为陌生人社交领域的主流应用&#xff0c;涵盖了多种核心业务模块&#xff0c;包括直播服务、附近动态功能、即时通讯&#xff08;IM&am…

【脑电信号处理与特征提取】P2-夏晓磊:脑电的神经起源与测量

夏晓磊&#xff1a;脑电的神经起源与测量 专业术语 electroencephalography(EEG) 脑电图 Excitatory Postsynaptic Potential(EPSP) 兴奋性突触后电位 Electrocorticography(ECoG) 皮层脑电图 什么是脑电/脑电图&#xff08;EEG&#xff09;&#xff1f; Electroencephalograp…

STM32F407移植OpenHarmony笔记1

参考文档&#xff1a; OpenAtom OpenHarmonywidthdevice-width,initial-scale1.0https://docs.openharmony.cn/pages/v3.2/zh-cn/device-dev/get-code/gettools-acquire.md/ 搭建环境 安装linux系统: Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-91-generic x86_64) 下载源代码&a…

鸿蒙自定义Video播放器

前言 DevEco Studio版本&#xff1a;4.0.0.600 使用效果 如何使用 参考文档&#xff1a;OpenHarmony Video使用说明 1、module创建 File-->New-->Module&#xff0c;选择Static Library 2、相关类创建 PlayControl&#xff1a;视频播放控制类 PlayProgress&#xf…

淘宝扭蛋机小程序开发:从创意到实现

一、引言 近年来&#xff0c;随着移动互联网的快速发展&#xff0c;小程序已成为商家与消费者互动的重要平台。其中&#xff0c;扭蛋机小程序以其独特的互动性和趣味性&#xff0c;受到了广泛的欢迎。本文将详细介绍淘宝扭蛋机小程序的开发过程&#xff0c;包括创意产生、需求…

OSPF-(LSA+SPF)

Router LSA使用Link来承载路由器直连接口的信息。 Link Type&#xff1a;P2P&#xff0c;TransNet&#xff0c;StubNet。 Point-to-Point&#xff08;P2P&#xff09;&#xff1a;描述一个从本路由器到邻居路由器之间的点到点链路&#xff1b;属于网段信息&#xff1b; StubN…

mmpose 2d姿态预测值转json文件

目录 效果图: 参考 模板文件下载地址: python预测代码: 效果图: <

【Flink-1.17-教程】-【四】Flink DataStream API(7)输出算子(Sink)

【Flink-1.17-教程】-【四】Flink DataStream API&#xff08;7&#xff09;输出算子&#xff08;Sink&#xff09; 1&#xff09;连接到外部系统2&#xff09;输出到文件3&#xff09;输出到 Kafka4&#xff09;输出到 MySQL&#xff08;JDBC&#xff09;5&#xff09;自定义 …

数论问题(算法村第十三关黄金挑战)

辗转相除法 8 和 12 的最大公因数是 4&#xff0c;记作 gcd(8,12)4。辗转相除法最重要的规则是&#xff1a; 若 mod 是 a b 的余数, 则gcd(a, b) gcd(b, mod)&#xff0c;直到a % b 0时&#xff0c;返回 b的值 gcd(546, 429) gcd(429, 117) gcd(117, 78) gcd(78, 39) …

Pytorch神经网络模型nn.Sequential与nn.Linear

1、定义模型 对于标准深度学习模型&#xff0c;我们可以使用框架的预定义好的层。这使我们只需关注使用哪些层来构造模型&#xff0c;而不必关注层的实现细节。 我们首先定义一个模型变量net&#xff0c;它是一个Sequential类的实例。 Sequential类将多个层串联在一起。 当给…

Redis:定时清理垃圾图片

首先理解清理垃圾文件的原理 在填写表单信息时上传图片后就已经存入云中&#xff0c;但是此时取消表单的填写这个图片就变成垃圾图片&#xff0c;所以在点击新建填写表单的方法/upload中&#xff0c;把上传的图片名字存入Redis的value中&#xff0c;key&#xff08;key1&#…

深入理解badblocks

文章目录 一、概述二、安装2.1、源码编译安装2.2、命令行安装2.3、安装确认 三、重要参数详解3.1、查询支持的参数3.2、参数说明 四、实例4.1、全面扫描4.2、破坏性写入并修复4.3、非破坏性写入测试 五、实现原理六、注意事项 团队博客: 汽车电子社区 一、概述 badblocks命令是…

NODE笔记 2 使用node操作飞书多维表格

前面简单介绍了node与简单的应用&#xff0c;本文通过结合飞书官方文档 使用node对飞书多维表格进行简单的操作&#xff08;获取token 查询多维表格recordid&#xff0c;删除多行数据&#xff0c;新增数据&#xff09; 文章目录 前言 前两篇文章对node做了简单的介绍&#xff…