一,方法中使用try...catch导致@Transactional事务无效的解决方法

1,问题的描述:

如果一个方法添加了@Transactional注解声明事务,

而方法内又使用了try catch 捕捉异常,

则方法内的异常捕捉会覆盖事务对异常的判断,

从而异致事务失效而不回滚

2, 如何解决?

第一个方法:给@Transactional注解增加:rollbackFor后并手动抛出指定的异常

第二个方法:在捕捉到异常后手动rollback

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的相关信息

1,项目地址:

https://github.com/liuhongdi/transactional

2,功能说明:

演示了事务方法中捕捉异常时如何使事务回滚

3,项目结构:如图:

三,配置文件说明

1,pom.xml

        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <!--mybatis begin-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency> <!--mysql begin-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

2,application.properties

#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/store?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #mybatis
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
mybatis.type-aliases-package=com.example.demo.mapper

3,创建数据表goods的sql

CREATE TABLE `goods` (
`goodsId` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`goodsName` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT 'name',
`subject` varchar(200) NOT NULL DEFAULT '' COMMENT '标题',
`price` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
`stock` int(11) NOT NULL DEFAULT '0' COMMENT 'stock',
PRIMARY KEY (`goodsId`),
UNIQUE KEY `goodsName` (`goodsName`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品表'

注意goodsName字段上有一个唯一的索引,
后面我们会利用它来引发一个duplicate entry 异常

插入一条数据:

INSERT INTO `goods` (`goodsId`, `goodsName`, `subject`, `price`, `stock`) VALUES
(3, '100分电动牙刷', '好用到让你爱上刷牙', '59.00', 100);

四,java代码说明

1,LockController.java

@RestController
@RequestMapping("/lock")
public class LockController { @Resource
OrderService orderService; //购买商品,方法内没有捕捉异常
@GetMapping("/lock")
@ResponseBody
public String buyLock() {
try {
int goodsId = 3;
orderService.decrementProductStoreLock(goodsId,1);
return "success";
} catch (Exception e){
System.out.println("捕捉到了异常 in controller");
e.printStackTrace();
String errMsg = e.getMessage();
return errMsg;
}
} //购买商品,方法内捕捉异常
@GetMapping("/lockcatch")
@ResponseBody
public String buyLockCatch() {
try {
int goodsId = 3;
boolean isDecre = orderService.decrementProductStoreLockWithCatch(goodsId,1);
if (isDecre) {
return "success";
} else {
return "false";
}
} catch (Exception e){
System.out.println("捕捉到了异常 in controller");
e.printStackTrace();
String errMsg = e.getMessage();
return errMsg;
}
} //购买商品,方法内捕捉异常
@GetMapping("/lockcatch1")
@ResponseBody
public String buyLockCatch1() {
try {
int goodsId = 3;
boolean isDecre = orderService.decrementProductStoreLockWithCatch1(goodsId,1);
if (isDecre) {
return "success";
} else {
return "false";
}
} catch (Exception e){
System.out.println("捕捉到了异常 in controller");
e.printStackTrace();
String errMsg = e.getMessage();
return errMsg;
}
} //购买商品,方法内捕捉异常
@GetMapping("/lockcatch2")
@ResponseBody
public String buyLockCatch2() {
try {
int goodsId = 3;
boolean isDecre = orderService.decrementProductStoreLockWithCatch2(goodsId,1);
if (isDecre) {
return "success";
} else {
return "false";
}
} catch (Exception e){
System.out.println("捕捉到了异常 in controller");
e.printStackTrace();
String errMsg = e.getMessage();
return errMsg;
}
} }

2,GoodsMapper.java

@Repository
@Mapper
public interface GoodsMapper {
Goods selectOneGoods(int goods_id);
int updateOneGoodsStock(Goods goodsOne);
int insertOneGoods(Goods goodsOne);
}

3,Goods.java

public class Goods {
//商品id
private int goodsId;
public int getGoodsId() {
return this.goodsId;
}
public void setGoodsId(int goodsId) {
this.goodsId = goodsId;
} //商品名字
private String goodsName;
public String getGoodsName() {
return this.goodsName;
}
public void setGoodsName(String goodsName) {
this.goodsName = goodsName;
} //商品库存数
private int stock;
public int getStock() {
return this.stock;
}
public void setStock(int stock) {
this.stock = stock;
}
}

4,OrderService.java

public interface OrderService {
public boolean decrementProductStoreLock(int goodsId, int buyNum);
public boolean decrementProductStoreLockWithCatch(int goodsId, int buyNum);
public boolean decrementProductStoreLockWithCatch1(int goodsId, int buyNum);
public boolean decrementProductStoreLockWithCatch2(int goodsId, int buyNum);
}

5,OrderServiceImpl.java

@Service
public class OrderServiceImpl implements OrderService { @Resource
private GoodsMapper goodsMapper; /*
*
* 减库存,供其他方法调用
* */
private boolean decrestock(int goodsId, int buyNum) {
Goods goodsOne = goodsMapper.selectOneGoods(goodsId);
System.out.println("-------------------------当前库存:"+goodsOne.getStock()+"-------购买数量:"+buyNum);
if (goodsOne.getStock() < buyNum || goodsOne.getStock() <= 0) {
System.out.println("------------------------fail:buy fail,return");
return false;
}
int upStock = goodsOne.getStock()-buyNum;
goodsOne.setStock(upStock);
int upNum = goodsMapper.updateOneGoodsStock(goodsOne);
System.out.println("-------------------------success:成交订单数量:"+upNum); int insNum = goodsMapper.insertOneGoods(goodsOne);
System.out.println("-------------------------success:ins数量:"+insNum); return true;
} //方法内不做try catch捕捉异常,可以正常回滚
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ)
public boolean decrementProductStoreLock(int goodsId, int buyNum) {
return decrestock(goodsId, buyNum);
} //异常时不处理,会导致不回滚
@Override
@Transactional(rollbackFor={Exception.class})
public boolean decrementProductStoreLockWithCatch(int goodsId, int buyNum) {
try {
return decrestock(goodsId, buyNum);
} catch (Exception e) {
System.out.println("捕捉到了异常in service method");
e.printStackTrace();
String errMsg = e.getMessage();
//throw e;
return false;
}
} //异常时手动抛出异常
@Override
@Transactional(rollbackFor={Exception.class})
public boolean decrementProductStoreLockWithCatch1(int goodsId, int buyNum) {
try {
return decrestock(goodsId, buyNum);
} catch (Exception e) {
System.out.println("捕捉到了异常in service method");
e.printStackTrace();
String errMsg = e.getMessage();
throw e;
}
} //异常时手动rollback
@Override
@Transactional(isolation = Isolation.REPEATABLE_READ)
public boolean decrementProductStoreLockWithCatch2(int goodsId, int buyNum) {
try {
return decrestock(goodsId, buyNum);
} catch (Exception e) {
System.out.println("捕捉到了异常in service method");
e.printStackTrace();
String errMsg = e.getMessage();
//手动rollback
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return false;
}
}
}

说明:decrestock这个方法中有两个写操作,分别是:减库存和insert一条商品记录,

后者会引发duplicate entry异常

6,GoodsMapper.xml

<?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.transactional.demo.mapper.GoodsMapper">
<select id="selectOneGoods" parameterType="int" resultType="com.transactional.demo.pojo.Goods">
select * from goods where goodsId=#{goodsId}
</select> <update id="updateOneGoodsStock" parameterType="com.transactional.demo.pojo.Goods">
UPDATE goods SET
stock = #{stock}
WHERE goodsId = #{goodsId}
</update> <insert id="insertOneGoods" parameterType="com.transactional.demo.pojo.Goods" useGeneratedKeys="true" keyProperty="goodsId">
insert into goods(goodsName) values( #{goodsName})
</insert>
</mapper>

五,测试效果

1,测试正常回滚:访问:

http://127.0.0.1:8080/lock/lock

返回:

### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '100分电动牙刷' for key 'goodsName' 
### The error may involve com.transactional.demo.mapper.GoodsMapper.insertOneGoods-Inline
### The error occurred while setting parameters
### SQL: insert into goods(goodsName) values( ?)
### Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '100分电动牙刷' for key 'goodsName' ;
Duplicate entry '100分电动牙刷' for key 'goodsName';
nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '100分电动牙刷' for key 'goodsName'

查看数据表:

库存未减少,说明正常回滚

查看控制台:

-------------------------当前库存:100-------购买数量:1
-------------------------success:成交订单数量:1
捕捉到了异常 in controller

2,测试事务无效,不能正常回滚:访问:

http://127.0.0.1:8080/lock/lockcatch

返回:

false

查看数据表:

回滚失效

查看控制台:

-------------------------当前库存:100-------购买数量:1
-------------------------success:成交订单数量:1
捕捉到了异常in service method

3,测试捕捉到异常后手动抛出异常引发回滚:

访问:

http://127.0.0.1:8080/lock/lockcatch1

返回:

### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '100分电动牙刷' for key 'goodsName'
### The error may involve com.transactional.demo.mapper.GoodsMapper.insertOneGoods-Inline
### The error occurred while setting parameters
### SQL: insert into goods(goodsName) values( ?)
### Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '100分电动牙刷' for key 'goodsName' ;
Duplicate entry '100分电动牙刷' for key 'goodsName';
nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry '100分电动牙刷' for key 'goodsName'

查看数据表:

查看控制台:

-------------------------当前库存:99-------购买数量:1
-------------------------success:成交订单数量:1
捕捉到了异常in service method
...
捕捉到了异常 in controller
...

4,测试捕捉到异常后手动回滚

访问:

http://127.0.0.1:8080/lock/lockcatch2

返回:

false

查看数据表:

查看控制台:

-------------------------当前库存:99-------购买数量:1
-------------------------success:成交订单数量:1
捕捉到了异常in service method

5,大家注意最后两个返回的区别:为什么会不一样?

手动抛出异常后,会被controller捕捉到,所以没有返回success或false,

而是返回了异常的提示信息

六,查看spring boot的版本:

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.4.RELEASE)

spring boot:方法中使用try...catch导致@Transactional事务无效的解决(spring boot 2.3.4)的更多相关文章

  1. Node.js中针对中文的查找和替换无效的解决方法

    Node.js中针对中文的查找和替换无效的解决方法.   //tags的值: tag,测试,帖子 var pos1 = tags.indexOf("测"); //这里返回-1 ta ...

  2. 面试突击83:什么情况会导致@Transactional事务失效?

    一个程序中不可能没有事务,而 Spring 中,事务的实现方式分为两种:编程式事务和声明式事务,又因为编程式事务实现相对麻烦,而声明式事务实现极其简单,所以在日常项目中,我们都会使用声明式事务 @Tr ...

  3. 【spring】在spring cloud项目中使用@ControllerAdvice做自定义异常拦截,无效 解决原因

    之前在spring boot服务中使用@ControllerAdvice做自定义异常拦截,完全没有问题!!! GitHub源码地址: 但是现在在spring cloud中使用@ControllerAd ...

  4. Spring Boot+AngularJS中因为跨域导致Session丢失

    http://blog.csdn.net/dalangzhonghangxing/article/details/52446821 如果还在为跨域问题烦恼,请查看博主的 解决angular+sprin ...

  5. Spring main方法中怎么调用Dao层和Service层的方法

    在web环境中,一般serviceImpl中的dao之类的数据库连接都由容器启动的时候创建好了,不会报错.但是在main中,没有这个环境,所以需要获取环境: ApplicationContext ct ...

  6. iOS - UITableView中Cell重用机制导致Cell内容出错的解决办法

    "UITableView" iOS开发中重量级的控件之一;在日常开发中我们大多数会选择自定Cell来满足自己开发中的需求, 但是有些时候Cell也是可以不自定义的(比如某一个简单的 ...

  7. 资料汇总--Ajax中Put和Delete请求传递参数无效的解决方法(Restful风格)【转】

    开发环境:Tomcat9.0 在使用Ajax实现Restful的时候,有时候会出现无法Put.Delete请求参数无法传递到程序中的尴尬情况,此时我们可以有两种解决方案:1.使用地址重写的方法传递参数 ...

  8. Ajax中Put和Delete请求传递参数无效的解决方法(Restful风格)

    本文装载自:http://blog.csdn.net/u012737182/article/details/52831008    感谢原文作者分享 开发环境:Tomcat9.0 在使用Ajax实现R ...

  9. jQuery中hover和blur使用代理delegate无效的解决方法

    今天就遇到了这样的小问题: $(document).ready(function(){ $('.status_on').hover(function(){ $(this).html('点击禁用'); ...

随机推荐

  1. pyhton:操作redis

    一.redis介绍 redis是一种非关系型数据库:没有表结构,没有字段,没有sql语句.只是用get获取数据,set插数据,类似字典.比如mangodb,redis redis的数据全存在内存,re ...

  2. Fragment时长统计那些事

    注:本文同步发布于微信公众号:stringwu的互联网杂谈 frament时长统计那些事 页面停留时长作为应用统计的北极星指标里的重要指标之一,统计用户在某个页面的停留时长则变得很重要.而Fragme ...

  3. 在windows下使用pip安装python包遇到缺失stdint.h文件的错误

    今天在windows上使用pip安装一个python包python-lzf时遇到如下的错误: fatal error C1083: Cannot open include file: 'stdint. ...

  4. 使用Java Stream,提取集合中的某一列/按条件过滤集合/求和/最大值/最小值/平均值

    不得不说,使用Java Stream操作集合实在是太好用了,不过最近在观察生产环境错误日志时,发现偶尔会出现以下2个异常: java.lang.NullPointerException java.ut ...

  5. 微信小程序 | 模仿百思不得其姐

    微信小程序 仿百思不得姐 设备 微信开发者工具 v1.02.1901230 扩展 修复了视频点击播放不流畅的问题 修复了视频的暂停够无法播放问题 优化了部分页面 接口 首页 http://api.bu ...

  6. 学习 | css3实现进度条加载

    进度条加载是页面加载时的一种交互效果,这样做的目的是提高用户体验. 进度条的的实现分为3大部分:1.页面布局,2.进度条动效,3.何时进度条增加. 文件目录 加载文件顺序 <link rel=& ...

  7. python语句与函数

    赋值语句 : 分支语句 : 函数 :根据输入参数产生不同输出功能 程序的输入与输出 input() 从控制台获得用户输入的函数 使用格式 print()函数 以字符形式向控制台输出结果的函数 字符类型 ...

  8. docker之windows安装&centOS安装

    按这个安装  没什么毛病 https://blog.csdn.net/vitaair/article/details/80894890 https://www.runoob.com/docker/ce ...

  9. java学习从“菜鸟”到“放弃”

    今天学到java的对象和类中, 由于刚考完c++面向对象与程序设计这门课,对于c++中的类掌握自认为不错,就开始过渡到java. 今天面对的问题,在书写一个类的时候,发现了许多与c++不同的地方. 比 ...

  10. 【转】Locust 性能测试-小案例(1)-环境搭建

    说在前面的话:从这节课开始,将讲解Locust作为一款测试工具,要怎么去应用.首先是"小案例"的系列文章,主要是给大家讲解locustfile也就是场景模拟的一些模式和方法.等到& ...