飞道的博客

Spring Boot事务配置管理

364人阅读  评论(0)

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 的地方加锁,保证锁 的范围比事务的范围大即可。


转载:https://blog.csdn.net/lin1214000999/article/details/105729489
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场