SpringBoot 事务回滚注意事项

这篇具有很好参考价值的文章主要介绍了SpringBoot 事务回滚注意事项。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

参考资料

  1. 导致 Spring 事务失效常见的几种情况
  2. SpringBoot2异常处理回滚事务详解(自动回滚/手动回滚/部分回滚)
  3. Spring,为内部方法新起一个事务,此处应有坑。
  4. PlatformTransactionManager
  5. Spring 事务管理及失效总结
  6. 我认真总结并分析了 Spring 事务失效的十种常见场景
  7. SpringBoot AOP配置全局事务


一. 需求

最近在项目中遇到了一个需求,需要读取csv的数据到数据库的临时表,
然后调用存储过程将临时表中的数据插入正式的数据库中。
⏹在将csv数据插入临时表之前需要将该表中的数据清空,如果在插入临时表时出现了问题,同样需要将表中的数据清空。
⏹需要对前台传入的数据进行业务校验,如果校验失败,需要抛出自定义异常,同时临时表中的数据不能回滚,需要被清空。
⏹在调用存储过程时,如果发生异常,则事务回滚。
需要注意的是,只回滚存储过程所涉及的表,临时表中被删除的数据并不参与回滚,直接删除。


二. 前期准备

2.1 Mapper

import java.util.Map;

public interface TestMapper02 {

    void deleteAllTempWork();

    void insertData(Map<String, Object> dataMap);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.TestMapper02">

    <delete id="deleteAllTempWork">
        DELETE FROM
          temp_work
    </delete>
    
    <insert id="insertData" parameterType="map">
    	INSERT 
    	INTO day16.`user`(id, username, birthday, email) 
		VALUES (#{id}, #{username}, #{birthday}, #{email})
    </insert>

</mapper>

2.2 自定义异常

public class ValidationException extends RuntimeException {
	private static final long serialVersionUID = 1L;
}

2.3 前台

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事务测试</title>
</head>
<body>
    <button id="btn1">点击发送请求</button>
</body>
<script th:src="@{/js/public/jquery-3.6.0.min.js}"></script>
<script th:inline="javascript">
	$("#btn1").click(function() {
		
		const data = {
			from: 13,
			to: 14
		}
		
	    $.ajax({
	        url: "/test02/transactional",
	        type: 'POST',
	        data: JSON.stringify(data),
	        contentType: 'application/json;charset=utf-8',
	        success: function (data, status, xhr) {
	            console.log(data);
	        }
	    });
	});
</script>
</html>

2.4 Controller层

import java.util.Map;
import javax.annotation.Resource;

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/test02")
public class Test02Controller {
	
	@Resource
	private Test02Service service;

	@GetMapping("/init")
	public ModelAndView init() {
		
		ModelAndView modelAndView = new ModelAndView();
		modelAndView.setViewName("test02");
		return modelAndView;
	}
	
	@PostMapping("/transactional")
	public ResponseEntity<Void> transactional(@RequestBody Map<String, Integer> data) throws Exception {
		
		// 测试事务
		service.transactional1(data);
		
		// service.transactional2(data);
		// service.transactional3(data);
		
		// 无响应给前台
		return ResponseEntity.noContent().build();
	}
}

三. 错误的使用方式

  • transactional1主方法调用同一个类中的insertData子方法,并且给子方法添加了@Transactional注解。
  • 出错的原因:
    • 在应用系统调用声明 @Transactional的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,再由这个代理对象来统一管理。
    • Spring 事务是使用 AOP 环绕通知和异常通知,就是对方法进行拦截,在方法执行前开启事务,在捕获到异常时进行事务回滚,在方法执行完成后提交事务。
    • 在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。
    • 👉👉👉spring是通过aop的方式,对需要spring管理事务的bean生成了代理对象,然后通过代理对象拦截了目标方法的执行,在方法前后添加了事务的功能,所以必须通过代理对象调用目标方法的时候,事务才会起效。
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.mapper.TestMapper02;

@Service
public class Test02Service {
	
	// 准备要向数据库插入的数据
	private final static Map<String, Object> dataMap = new HashMap<>() {
		private static final long serialVersionUID = 1L;
		{
			put("id", 99);
			put("username", "test99");
			put("birthday", new Date());
			put("email", "test99@test.com");
		}
	};
	
	@Resource
	private TestMapper02 mapper02;
	
	// 存在事务的方法
	public void transactional1(Map<String, Integer> data) throws Exception {
		
		// 删除临时表中的所有数据
		mapper02.deleteAllTempWork();
		
		// 获取from和to的值
		Integer fromValue = data.get("from");
		Integer toValue = data.get("to");
		if (fromValue > toValue) {
			// 抛出异常
			throw new Exception();
		}
		
		this.insertData(dataMap);
	}
	
	/*
		insertData方法被transactional1方法调用
	*/
	@Transactional(rollbackFor = Exception.class)
	public void insertData(Map<String, Object> dataMap) {
	
		mapper02.insertData(dataMap);
		
		// 模拟出现异常
		int reslt = 1 / 0;
	}
}

四. 正确的使用方式

4.1 方式1-抽取新类(声明式事务)

⏹创建一个新类,将需要被事务管理的部分放到此类中

import java.util.Map;
import javax.annotation.Resource;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class Test02SubService {
	
	@Resource
	private TestMapper02 mapper02;
	
	@Transactional(rollbackFor = Exception.class)
	public void insertData(Map<String, Object> dataMap) throws Exception {
		
		mapper02.insertData(dataMap);
		
		// 模拟运行时异常
		int reslt = 1 / 0;
	}
}

⏹我们的主Service调用子Service,子Service由Spring来管理,会生成事务对象。文章来源地址https://www.toymoban.com/news/detail-627421.html

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.demo.mapper.TestMapper02;

@Service
public class Test02Service {
	
	// 准备要向数据库插入的数据
	private final static Map<String, Object> dataMap = new HashMap<>() {
		private static final long serialVersionUID = 1L;
		{
			put("id", 99);
			put("username", "test99");
			put("birthday", new Date());
			put("email", "test99@test.com");
		}
	};
	
	@Resource
	private Test02SubService subService;
	
	@Resource
	private TestMapper02 mapper02;
	
	// 存在事务的方法
	public void transactional1(Map<String, Integer> data) throws Exception {
		
		// 删除临时表中的所有数据
		mapper02.deleteAllTempWork();
		
		// 获取from和to的值
		Integer fromValue = data.get("from");
		Integer toValue = data.get("to");
		if (fromValue > toValue) {
			// 抛出异常
			throw new Exception();
		}
		
		// 如果调用时发生异常,只会回滚insertData方法中的内容
		subService.insertData(dataMap);
	}
}

4.2 方式2-编程式事务,事务管理器

package com.example.demo.service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

@Service
public class Test02Service {
	
	private final static Map<String, Object> dataMap = new HashMap<>() {
		private static final long serialVersionUID = 1L;
		{
			put("id", 99);
			put("username", "test99");
			put("birthday", new Date());
			put("email", "test99@test.com");
		}
	};
	
	@Resource
	private TestMapper02 mapper02;
	
	// 事务管理器
	@Autowired
	private DataSourceTransactionManager dataSourceTransactionManager;
	
	// 事务定义对象
	@Autowired
	private TransactionDefinition transactionDefinition;
	
	public void transactional2(Map<String, Integer> data) throws Exception {
		
		// 删除临时表,此部分不需要被事务管理
		mapper02.deleteAllTempWork();
		
		// 手动开启事务
		TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
		try {
			mapper02.insertData(dataMap);
			// 模拟异常
			int a = 1 / 0;
		} catch (Exception e) {
			// 手动回滚事务
			dataSourceTransactionManager.rollback(transactionStatus);
			// 抛出异常,防止程序继续执行
			throw new Exception();
		}
		
		// 若没有问题则,则提交事务
		dataSourceTransactionManager.commit(transactionStatus);
	}	
}

4.3 方式3-编程式事务,设置回滚点

  • 通过TransactionAspectSupport设置回滚点
  • rollbackFor = Exception.class:指定该异常需要回滚
  • noRollbackFor = ValidationException.class:指定该异常不需要回滚
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

@Service
public class Test02Service {
	
	private final static Map<String, Object> dataMap = new HashMap<>() {
		private static final long serialVersionUID = 1L;
		{
			put("id", 99);
			put("username", "test99");
			put("birthday", new Date());
			put("email", "test99@test.com");
		}
	};
	
	@Resource
	private TestMapper02 mapper02;
	
	// 指定抛出Exception异常时回滚;指定抛出ValidationException异常时不回滚
	@Transactional(rollbackFor = Exception.class, noRollbackFor = ValidationException.class)
	public void transactional3(Map<String, Integer> data) throws Exception {
		
		// 如果出现了任何异常就进行捕获,然后就抛出自定义的ValidationException异常
		// 我们在声明式事务中指定了ValidationException异常不进行回滚,
		// 因此就算此处的try catch块中的异常被抛出,此处的事务也不会进行回滚
		try {
			mapper02.deleteAllTempWork();
		} catch (Exception e) {
			throw new ValidationException();
		}
		
		// 设置事务的回滚点
		Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
		
		try {
			mapper02.insertData(dataMap);
			// 模拟运行时异常
			int a = 1 / 0;
		} catch (Exception e) {
			// 当发生异常的时候,将事务回滚到预设的回滚点处
			TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
		}
	}
}

五. 事务失效的几种情况

  1. @EnableTransactionManagement注解未启用
  2. 方法不是public类型
    • @Transaction 可以用在类上、接口上、public方法上,如果将@Trasaction用在了非public方法上,事务将无效。
  3. 三. 错误的使用方式所示的自调用情况
    • spring是通过aop的方式,对需要spring管理事务的bean生成了代理对象,然后通过代理对象拦截了目标方法的执行,在方法前后添加了事务的功能,所以必须通过代理对象调用目标方法的时候,事务才会起效。
  4. 抛出的异常被捕获
    • 当业务方法抛出异常,spring感知到异常的时候,才会做事务回滚的操作,若方法内部将异常给吞了,那么事务无法感知到异常了,事务就不会回滚了。
  5. 数据库非InnoDB引擎
    • 只有InnoDB引擎才支持事务,而MyISAM引擎是不支持事务的

到了这里,关于SpringBoot 事务回滚注意事项的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringBoot整合Elasticsearch实现分页条件查询及注意事项

    项目环境: springboot 2.3.7.RELEASE es 6.8.3 这里需要注意es中日期格式,ES默认是不支持yyyy-MM-dd HH:mm:ss格式的,需要通过 @Field(type = FieldType.Date, format = DateFormat.custom,pattern = \\\"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_second\\\") 来指定日期格式。 直接看业务层实现分页条件查询: 范围查询: es en

    2023年04月16日
    浏览(49)
  • 微服务 Spring Cloud 1,服务如何拆分?使用微服务的注意事项?

    大家好,我是哪吒。 微服务已经是Java开发的必备技能,甲方不管项目大小,都想上微服务,感觉上了就高大上了,牛逼了。 微服务确实给我们带来了一定的便利性,但是也带来了麻烦,比如学习成本高,存在很多不可预见的问题。 我是做互联网项目的,刚开始的时候,用的

    2024年01月19日
    浏览(40)
  • SpringBoot 使用validator进行参数校验(实例操作+注意事项+自定义参数校验)

    ①、引入依赖 ②、创建实体类 ③、建立控制层 ④、进行测试 作为测试demo,到这里就可以直接测试了 ①、@NotNull ,@NotEmpty 和 @NotBlank 三者的区别 @NotNull、@NotEmpty 和 @NotBlank 都是用于Java中进行参数校验的注解,它们之间的区别如下: @NotNull 注解用于限制参数不能为null。 @N

    2024年02月07日
    浏览(46)
  • 深入了解 Spring boot的事务管理机制:掌握 Spring 事务的几种传播行为、隔离级别和回滚机制,理解 AOP 在事务管理中的应用

    🎉🎉欢迎光临,终于等到你啦🎉🎉 🏅我是苏泽,一位对技术充满热情的探索者和分享者。🚀🚀 🌟持续更新的专栏 《Spring 狂野之旅:从入门到入魔》 🚀 本专栏带你从Spring入门到入魔   这是苏泽的个人主页可以看到我其他的内容哦👇👇 努力的苏泽 http://suzee.blog.csdn

    2024年03月21日
    浏览(80)
  • 学习Linux的注意事项(使用经验;目录作用;服务器注意事项)

    本篇分享学习Linux过程中的一些经验 Linux严格区分大小写 Linux中所有内容以文件形式保存 ,包括硬件,Linux是以管理文件的方式操作硬件 硬盘文件是 /dev/sd[a-p] 光盘文件是 /dev/sr0 等 对于设置需要写入文件,命令行的设置在重启之后就会失效,只有下入文件才可以保存下来 文

    2024年02月11日
    浏览(69)
  • 弱电线布线注意什么?弱电线布线的注意事项

    弱电 弱电一般是指直流电路或音频、视频线路、网络线路、电话线路,直流电压一般在36V以内。家用电器中的电话、电脑、电视机的信号输入(有线电视线路)、音响设备(输出端线路)等用电器均为弱电电气设备。 弱电线的种类如:电话线、网络线、有线电视线及音响线

    2024年02月07日
    浏览(44)
  • RabbitMQ开发注意事项

    在使用 RabbitMQ 进行消息队列的开发过程中,有一些注意事项需要牢记: 安全性:确保正确配置 RabbitMQ 实例的安全性。限制对 RabbitMQ 服务器的访问权限,并使用安全的认证机制(如用户名和密码)来保护连接。 错误处理:在消费者端,务必处理可能发生的异常或错误情况。

    2024年02月07日
    浏览(48)
  • 算法竞赛个人注意事项

    浅浅记录一下自己在算法竞赛中的注意事项。 注意看数大小,数学库中的函数尽量加上 * 1.0 , 转成double,防止整型溢出。 , int 型相乘如果可能溢出,乘 * 1LL 。 数据范围大于1e6,注意用快读。 浮点数输入输出: 取模,注意取成负数的情况。 全 int ,但是数据太大,全转

    2024年02月09日
    浏览(50)
  • 面试求职-面试注意事项

    面试技巧和注意事项有哪些? 面试是找工作过程中最重要的一个环节,因为面试成功,你才有可能得到一份工作。求职面试技巧有哪些呢?首先,我们来看看面试注意事项。 1、面试前有没有仔细了解过对应企业的情况,对方的企业文化、主营业务、未来的发展方向。如果跑过

    2024年02月09日
    浏览(38)
  • 低代码选型注意事项

    凭借着革命性的生产力优势,低代码技术火爆了整个IT圈。面对纷繁复杂的低代码和无代码产品,开发者该如何选择? 在研究低代码平台的年数上,本人已有3年,也算是个低代码资深用户了,很多企业面临低代码选型上的困难,选平台容易,换平台难。下面基于个人理解给大

    2024年02月03日
    浏览(55)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包