SpringBoot中的事务配置管理详解
作者:Mu_Mu是一只小白
Spring Boot事务配置管理
场景:我们在开发企业应用时,由于数据操作在顺序执行的过程中,线上可能有各种无法预知的问题,
任何一步操作都有可能发生异常,异常则会导致后续的操作无法完成。此时由于业务逻辑并未正确的完
成,所以在之前操作过数据库的动作并不可靠,需要在这种情况下进行数据的回滚。
1. 导入依赖
springboot的事务管理需要导入spring-boot-starter-jdbc;而我们导入的mybatis-spring-boot-starter包含了它,所以无需重复导入;
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency>
2. 事务测试
1)mapper接口
@Insert("insert into tb_filminfo (typeid,filmname,ticketprice) values(#{typeid},#{filmname},#{ticketprice})") int insert(FilminfoPO po);
2)service接口
public interface IFilmInfoService { //插入一条记录 public int insert(FilminfoPO po);
3)service实现类
@Service public class FilmInfoServiceImpl implements IFilmInfoService { @Resource private FilmInfoMapper mapper; @Transactional @Override public int insert(FilminfoPO po) { return mapper.insert(po); }
4)controller类
@Controller @RequestMapping("/film") public class FilmInfoController { @Resource IFilmInfoService service; @RequestMapping("/insert") public String insert(FilminfoPO po) { if (po!=null) { int i = service.insert(po); return "success"; } else { return "false"; } } }
当没有异常抛出时添加成功,有异常出现添加失败;
3. 事务处理的一些特殊情况
1)异常并没有被捕获到
异常并没有被 ”捕获“ 到,导致事务并没有回滚。
Spring Boot 默认的事务规则是遇到运行异常(RuntimeException)和程序 错误(Error)才会回滚。但是抛出 SQLException 就无法回滚了。
@Transactional @Override public void insert(FilminfoPO po) throws SQLException { // 手动抛出异常 mapper.insert(po); throw new SQLException("数据库异常");
虽然抛出异常但是数据插入成功;
解决方案:针对非运行时异常,如果要进行事务回滚的话,可以在 @Transactional 注解中使用 rollbackFor 属性来指定异常,比如 @Transactional(rollbackFor = Exception.class) ,这样就没有问题了,所以在实际项目中,一定要指定异常。
@Transactional(rollbackFor = Exception.class) @Override public void insert(FilminfoPO po) throws SQLException { // 手动抛出异常 mapper.insert(po); throw new SQLException("数据库异常"); }
这样就插入失败了;
2)异常在方法中被捕获导致事务回滚失败
我们在处理异常时,有两种方式, 要么抛出去,让上一层来捕获处理;要么把异常 try catch 掉,在异常出现的地方给处理掉。就因为有 这中 try…catch,所以导致异常被 ”吃“ 掉,事务无法回滚。
@Transactional @Override public int insert(FilminfoPO po) { try { int i = 1 / 0; } catch (Exception e) { e.getMessage(); } return mapper.insert(po); }
记录成功被插入;
解决方法:直接往上抛,给上一层来处理即可
3)事务的范围 冲突导致回滚失败
许多业务需要在高并发的情况下保证数据唯一性所比要加synchronized关键字如一个数据库中,针对某个用户,只有一条记录,下一个插入动作过来,会先判断该数据库 中有没有相同的用户,如果有就不插入,就更新,没有才插入,所以理论上,数据库中永远就一条同一 用户信息,不会出现同一数据库中插入了两条相同用户的信息。
@Transactional(rollbackFor = Exception.class) @Override public synchronized void insert(FilminfoPO po) throws SQLException { // 手动抛出异常 mapper.insert(po); }
但是在压测时,数据库中确实可能有两条同一用户的信息,分析其原因,在于事务的
范围和锁的范围问题。
在执行该方法开始时,事务启动,执行 完了后,事务关闭。但是 synchronized 没有起作用,其实根本原因是因为事务的范围比锁的范围大。 也就是说,在加锁的那部分代码执行完之后,锁释放掉了,但是事务还没结束,此时另一个线程进来 了,事务没结束的话,第二个线程进来时,数据库的状态和第一个线程刚进来是一样的。即由于mysql Innodb引擎的默认隔离级别是可重复读(在同一个事务里,SELECT的结果是事务开始时时间点的状 态),线程二事务开始的时候,线程一还没提交完成,导致读取的数据还没更新。第二个线程也做了插 入动作,导致了脏数据。
解决方案:
1.把事务去掉即可(不推荐);
2. 在调用该 service 的地方加锁,保证锁 的范围比事务的范围大即可。
到此这篇关于SpringBoot中的事务配置管理详解的文章就介绍到这了,更多相关SpringBoot事务配置内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!