文章目录

  • 1.事务
  • 2.SpringBoot事务配置
    • 2.1 依赖导入
    • 2.2 事务的测试
  • 3.常见问题总结
    • 3.1 异常并没有被捕获到
    • 3.2 异常被“吃”掉
    • 3.3 事务的范围

1.事务

使用功能场景:
由于数据操作在顺序执行的过程中,线上可能有各种无法预知的问题,任何一步操作都有可能发生异常,异常则会导致后续的操作无法完成,此时由于业务逻辑并未正确的完成,所以在之前操作数据库的动作并不可靠,需要在这种情况下进行数据的回滚。
事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务未进行操作的状态。
事务管理是SpringBoot框架中最为常用的功能之一,我们在实际应用开发时,基本上在service层处理业务逻辑的时候都要加上事务,当然了,有时候可能由于场景需要,也不用加事务(比如我们往一个表里插数据,相互没有影响,插多少是多少,不能因为某个数据挂了,把之前插的全部回滚)

2.SpringBoot事务配置

2.1 依赖导入

在SpringBoot中使用事务,需要导入mybatis依赖:

        <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.0</version></dependency>

导入了mybatis依赖后,SpringBoot会自动注入DataSourceTransactionManager,我们不需要任何其他的配置就可以用@Transactional注解进行事务的使用,关于MyBatis的配置,上文已经说明,这里和上文Mybatis配置一致即可

2.2 事务的测试

我们首先在数据库表中插入一条数据

然后我们写一个插入的mapper:

package com.example.springdemo1.dao;import com.example.springdemo1.pojo.User;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;@Mapper
@Repository
public interface UserMapper {@Insert("insert into user(id,name,pwd) values (#{id},#{name},#{pwd}")Integer insertUser(User user);
}

OK,接下来我们来测试一下SpringBoot中的事务处理,在service层,我们手动抛出个异常来模拟实际中出现的异常,然后观察一下事务有没有回滚,如果数据库中没有新的记录,则说明事务回滚成功

package com.example.springdemo1.service;import com.example.springdemo1.pojo.User;public interface UserService{void insertUser(User user);
}
package com.example.springdemo1.service.impl;import com.example.springdemo1.dao.UserMapper;
import com.example.springdemo1.pojo.User;
import com.example.springdemo1.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;@Service
public class UserServiceImpl implements UserService{@Resourceprivate UserMapper userMapper;@Override@Transactionalpublic void insertUser(User user) {//插入用户信息userMapper.insertUser(user);//手动抛出异常throw new RuntimeException();}
}

我们来测试一下:

package com.example.springdemo1.controller;import com.example.springdemo1.pojo.User;
import com.example.springdemo1.service.UserService;
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.bind.annotation.RestController;import javax.annotation.Resource;@RestController
@RequestMapping("/test")
public class testController11 {@Resourceprivate UserService userService;@PostMapping("/addUser")public String addUser(@RequestBody User user){if(user != null){userService.insertUser(user);return "success";}else{return "failure";}}
}

我们使用postman调用一下该接口,因为在程序中抛出了个异常,会造成事务回滚,我们刷新一下数据库,并没有增加一条记录,说明事务生效了。

3.常见问题总结

3.1 异常并没有被捕获到

首先要说的,就是异常并没有被捕获到,导致事务并没有回滚,我们在业务层代码中,也许已经考虑到了异常的存在,或者编辑器已经提示我们需要抛出异常,但是这里面有个需要注意的地方:并不是说我们把异常抛出来了,有异常了事务就会回滚,我们来看一个例子:

package com.example.springdemo1.service;import com.example.springdemo1.pojo.User;public interface UserService{void insertUser2(User user) throws Exception;
}
package com.example.springdemo1.service.impl;import com.example.springdemo1.dao.UserMapper;
import com.example.springdemo1.pojo.User;
import com.example.springdemo1.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.sql.SQLException;@Service
public class UserServiceImpl implements UserService{@Resourceprivate UserMapper userMapper;@Override@Transactionalpublic void insertUser2(User user) throws Exception {//插入用户信息userMapper.insertUser(user);//手动抛出异常throw new SQLException("数据库异常");}}

我们看上面这个代码,其实并没有什么问题,手动抛出一个SQLException来模拟实际中操作数据库发生的异常,在这个方法中,既然跑出了异常,那么事务应该回滚,实际却不如此,写个测试类测试一下

package com.example.springdemo1.controller;import com.example.springdemo1.pojo.User;
import com.example.springdemo1.service.UserService;
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.bind.annotation.RestController;import javax.annotation.Resource;@RestController
@RequestMapping("/test")
public class testController11 {@Resourceprivate UserService userService;//测试异常并没有被捕获到@PostMapping("/addUser2")public String adddUser2(@RequestBody User user) throws Exception{if(user != null){userService.insertUser2(user);return "success";}else{return "failure";}}
}

通过postman测试一下,就会发现,仍然是可以插入一条用户数据的,那么问题出在哪呢?因为SpringBoot默认的事务规则是遇到运行异常和程序错误才会回滚,比如上述例子中抛出的RuntimeException就没有问题,但是抛出SQLException就无法回滚了,针对非运行时异常,如果要进行事务回滚的话,可以在 @Transactional注解中使用rollbackFor属性来指定异常,比如@Transactional(rollbackFor = Exception.class),这样就没有问题了。

3.2 异常被“吃”掉

我们在处理异常时,有两种方式,要么抛出去,让上一层来捕获处理,要么把异常try catch掉,在异常出现的地方给处理掉,就因为有这种try catch,所以导致异常被“吃”掉,事务无法回滚
,我们还是看上面那个例子,简单修改一下代码:

package com.example.springdemo1.service;import com.example.springdemo1.pojo.User;public interface UserService{void insertUser3(User user);
}
package com.example.springdemo1.service.impl;import com.example.springdemo1.dao.UserMapper;
import com.example.springdemo1.pojo.User;
import com.example.springdemo1.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.sql.SQLException;@Service
public class UserServiceImpl implements UserService{@Resourceprivate UserMapper userMapper;@Override@Transactional(rollbackFor = Exception.class)public void insertUser3(User user) {user = new User(7,"lyh7","123456");try{//插入用户信息userMapper.insertUser(user);//手动抛出异常throw new SQLException("数据库异常");}catch(Exception e){//异常处理逻辑}}
}
package com.example.springdemo1.controller;import com.example.springdemo1.pojo.User;
import com.example.springdemo1.service.UserService;
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.bind.annotation.RestController;import javax.annotation.Resource;@RestController
@RequestMapping("/test")
public class testController11 {@Resourceprivate UserService userService;//测试异常被吃掉@PostMapping("/addUser3")public String addUser3(@RequestBody User user){if (user != null){userService.insertUser3(user);return "success";}else{return "failure";}}
}

通过postman测试一下,就会发现,仍然是可以插入一条用户数据,说明事务并没有因为抛出异常而回滚,那这种怎么解决呢?直接往上抛,给上一层来处理即可,千万不要在事务中把异常自己“吃”掉。

3.3 事务的范围

举个例子:

package com.example.springdemo1.service.impl;import com.example.springdemo1.dao.UserMapper;
import com.example.springdemo1.pojo.User;
import com.example.springdemo1.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import javax.annotation.Resource;
import java.sql.SQLException;@Service
public class UserServiceImpl implements UserService{@Resourceprivate UserMapper userMapper;@Override@Transactional(rollbackFor = Exception.class)public synchronized void insertUser4(User user){//实际中的具体业务userMapper.insertUser(user);}
}

可以看到,因为要考虑并发问题,在业务层代码的方法上加了个synchronized关键字,从上面方法中可以看到,方法上是加了事务的,那么也就是说,在执行该方法开始时,事务启动,执行完了之后,事务关闭,但是synchronized没有起作用,其实根本原因是因为事务的范围比锁的范围大,也就是说,在加锁的那部分代码执行完之后,锁释放掉了,但是事务还没有结束,此时另一个线程进来了,事务没结束的话,第二个线程进来时,数据库的状态和第一个线程刚进来是一样的,即由于mysql InnoDB引擎的默认隔离级别是可重复读(在同一个事务里,select的结果是事务开始时时间点的状态),线程二事务开始的时候,线程一还没提交完成,导致读取的数据还没更新呢,第二个线程也做了插入动作,导致了脏数据。

这个问题可以避免:

  • 把事务去掉即可
  • 在调用该service的地方加锁,保证锁的范围比事务的范围大即可

SpringBoot事务配置管理相关推荐

  1. springboot 事务回滚

    springboot 事务回滚 springboot 事务使用 springboot 事务使用 **1.**只有在开启事务的方法中出现异常,才会自动回滚,需要在service的public方法上面加上 ...

  2. springboot 事务统一配置_Spring Boot实现分布式微服务开发实战系列(五)

    最近已经推出了好几篇SpringBoot+Dubbo+Redis+Kafka实现电商的文章,今天再次回到分布式微服务项目中来,在开始写今天的系列五文章之前,我先回顾下前面的内容. 系列(一):主要说了 ...

  3. springboot 事务手动回滚_来,讲讲Spring事务有哪些坑?

    来自公众号:孤独烟 引言 今天,我们接上文<面试官:谈谈你对mysql事务的认识>的内容,来讲spring中和事务有关的考题! 因为事务这块,面试的出现几率很高.而大家工作中CRUD的比较 ...

  4. springboot事务回滚源码_Spring Boot中的事务是如何实现的

    1. 概述 一直在用SpringBoot中的@Transactional来做事务管理,但是很少想过SpringBoot是如何实现事务管理的,今天从源码入手,看看@Transactional是如何实现事 ...

  5. springboot 事务_原创002 | 搭上SpringBoot事务源码分析专车

    前言 如果这是你第二次看到师长,说明你在觊觎我的美色! 点赞+关注再看,养成习惯 没别的意思,就是需要你的窥屏^_^ 专车介绍 该趟专车是开往Spring Boot事务源码分析的专车 专车问题 为什么 ...

  6. 26.SpringBoot事务注解详解

    转自:https://www.cnblogs.com/kesimin/p/9546225.html @Transactional spring 事务注解 1.简单开启事务管理 @EnableTrans ...

  7. springboot 事务手动回滚_Spring Boot中的事务是如何实现的

    1. 概述 一直在用SpringBoot中的@Transactional来做事务管理,但是很少想过SpringBoot是如何实现事务管理的,今天从源码入手,看看@Transactional是如何实现事 ...

  8. springboot+事务,多张表的操作事务回滚

    第一步,在springboot的启动类上开启事务,注解 @EnableTransactionManagement 第二步:事务注解,回滚 @Transactional(rollbackFor = Ex ...

  9. springboot 事务_Spring Boot中的事务是如何实现的?懂吗?

    一个SpringBoot问题就干趴下了?我却凭着这份PDF文档吊打面试官. 金三银四第一天,啃透这些SpringBoot知识点,还怕干不赢面试官? Spring全家桶笔记:Spring+Spring ...

最新文章

  1. java 重写构造函数,覆盖Java中的默认构造函数
  2. 机器学习特征工程之连续变量离散化:聚类法进行分箱
  3. deepspeaker(TensorFlow)百度声纹识别和对比代码和模型
  4. interrupt分析
  5. 云计算背后的秘密(6)-NoSQL数据库的综述
  6. ASP.NET MVC 的一个配置节点
  7. JQuery UI - resizable
  8. 【iCore3 双核心板_FPGA】例程五:Signal Tapll实验——逻辑分析仪
  9. 机器学习算法总结--提升方法
  10. eclipse简单使用
  11. Bzoj5251: [2018多省省队联测]劈配
  12. 读芯术python答案_你爱 “Python”的身体,还是“R”的灵魂?
  13. C语言基础知识:printf的输出格式
  14. Go 大败!Google 宣布 Fuchsia 终端开发只支持 C/C++/Dart
  15. iOS学习01C语言数据类型
  16. zedgraph显示最小刻度_关于ZedGraph几个难点
  17. 华为+android+root权限获取root,华为emui5.0系统如何root?华为荣耀v8 emui5.0获取7.0系统的root权限方法...
  18. 【史上最全】常用USB转串口芯片特性比较
  19. 工作笔记八——vue项目的多语言/国际化插件vue-i18n详解
  20. Python编程:Python2和Python3环境下re正则匹配中文

热门文章

  1. word2016 明明设置了默认粘贴为“仅保留文本”,可是每次粘贴的时候还是带源格式怎么办?
  2. 2022按键精灵内存逆向新教程:乐玩篇:1-10课更新试看
  3. (科软)高级软件工程课程总结
  4. 50例源码Python scipy.stats.norm 模块,pdf()
  5. 【Java】 Java 实现 2048 小游戏
  6. RCN-Rich feature hierarchies for accurate object detection and semantic segmentation
  7. python 累加器_python中的累加器
  8. py语言和php,php和python什么区别
  9. 计算机视觉 专业术语,计算机视觉中常用的术语.doc
  10. 51Nod 2069 牛奶 c/c++题解