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日
    浏览(44)
  • 微服务 Spring Cloud 1,服务如何拆分?使用微服务的注意事项?

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

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

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

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

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

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

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

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

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

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

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

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

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

    2024年02月03日
    浏览(46)
  • 钢网的清洗注意事项

    在smt贴片加工的生产过程中,由于钢网受重力影响会变形、定位可能会有不准确、支撑没有到位或者是设计等其他问题,这样的话在锡膏印刷的时候钢网和电路板的焊盘之间很难形成理想的密封状态,在SMT贴片加工的过程中,会引起焊锡膏在钢网跟电路板的空隙间挤出来,并

    2024年02月11日
    浏览(26)
  • postman调试注意事项

    Postman是一个强大的API调试工具,它可以帮助开发人员测试和调试API端点,以确保它们按预期工作。在使用Postman进行接口调试时,以下是一些注意事项和可能出现的问题,以及如何解决这些问题。 确保请求参数正确 在测试API接口时,确保您提供了正确的请求参数非常重要。

    2024年02月10日
    浏览(32)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包