SpringBoot事务配置管理[通俗易懂]

SpringBoot事务配置管理[通俗易懂]1.事务使用功能场景:由于数据操作在顺序执行的过程中,线上可能有各种无法预知的问题,任何一步操作都有可能发生异常,异常则会导致后续的操作无法完成,此时由于业务逻辑并未正确的完成,所以在之前操作数据库的动作并不可靠,需要在这种情况下进行数据的回滚。事务的作用就是为了保证用户的每一个操作都是可靠的,事务中的每一步操作都必须成功执行,只要有发生异常就回退到事务未进行操作的状态。事务管理是SpringBoot框架中最为常用的功能之一,我们在实际应用开发时,基本上在service层处理业务逻辑的时候都要加上事

大家好,又见面了,我是你们的朋友全栈君。


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{ 
   

    @Resource
    private UserMapper userMapper;

    @Override
    @Transactional
    public 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 { 
   
    @Resource
    private 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{ 
   

    @Resource
    private UserMapper userMapper;

    @Override
    @Transactional
    public 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 { 
   
    @Resource
    private 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{ 
   

    @Resource
    private 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 { 
   
    @Resource
    private 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{ 
   

    @Resource
    private UserMapper userMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized void insertUser4(User user){ 
   
        //实际中的具体业务
        userMapper.insertUser(user);
    }
}

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

这个问题可以避免:

  • 把事务去掉即可
  • 在调用该service的地方加锁,保证锁的范围比事务的范围大即可
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/134307.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 钢琴初学者怎样自学五线谱_小星星五线谱钢琴

    钢琴初学者怎样自学五线谱_小星星五线谱钢琴适用于程序员的钢琴教程这是由山东琴律信息科技有限公司制作的‘钢琴五线谱入门教程’全集分为五集。分别为:这是由山东琴律信息科技有限公司制作的‘钢琴五线谱入门教程’全集分为五集。分别为:钢琴(带你认识钢琴,琴键,调式等)音高(把五线谱中的音高部分单独拿出来讲)音值(把音值部分也单独拿出来讲)音强(五线谱的音强部分)总结(把一首曲子分为音高音值和音强三个方面进行分解,并总结)有想学习钢琴,乐理,识谱方面知识的大家可以关注一下,对于我们理科出身的用户还是比较友好的。B站也有很多用户非常喜欢。所

  • 冒泡排序深入具体解释

    冒泡排序深入具体解释

  • oracle创建数据库实例失败_oracle数据库实例名是什么

    oracle创建数据库实例失败_oracle数据库实例名是什么Oracle数据库实例的创建、删除、修改   以SUSE10SP2、Oracle10gR2为例。      本文中的数据库实例这一称谓应该换做数据库更为准确,数据库可以理解为是一个物理的静态概念,主要包括一些物理存在的数据库文件,而数据库实例则是一个动态概念,包括一些内存区域以及若干进程,数据库实例是对数据库进行操作的执行者。(20090714修改)   安装完Ora

  • Python解决求最大公约数和最小公倍数问题

    Python解决求最大公约数和最小公倍数问题目录一.思路分析1.欧几里得法(辗转相除法)2.穷举法(一个一个除)3.stein算法二.提高要求三.测试截图题目:求两个正整数的最大公约数和最小公倍数。基本要求:1.程序风格良好(使用自定义注释模板),两种以上算法解决最大公约数问题,提供友好的输入输出。提高要求:1.三种以上算法解决两个正整数最大公约数问题。         2.求3个正…

  • idea2021.4.14版本永久激活码_通用破解码

    idea2021.4.14版本永久激活码_通用破解码,https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • HashTable的数组和连接两种实现方法(Java版本号)

    HashTable的数组和连接两种实现方法(Java版本号)

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号