码迷,mamicode.com
首页 > 编程语言 > 详细

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

时间:2020-10-18 16:17:38      阅读:27      评论:0      收藏:0      [点我收藏+]

标签:ack   collate   判断   rod   from   font   creat   tin   service   

一,方法中使用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)

标签:ack   collate   判断   rod   from   font   creat   tin   service   

原文地址:https://www.cnblogs.com/architectforest/p/13822505.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!