美文网首页
Spring Boot使用事务

Spring Boot使用事务

作者: 一个骚骚的码农 | 来源:发表于2021-11-01 09:05 被阅读0次

    序言:此前,我们主要通过XML配置Spring来托管事务。在SpringBoot则非常简单,只需在业务层添加事务注解(@Transactional )即可快速开启事务。虽然事务很简单,但对于数据方面是需要谨慎对待的,识别常见坑点对我们开发有帮助。
    <meta charset="utf-8">

    1. 引入依赖

        <!--依赖管理 -->
        <dependencies>
            <dependency> <!--添加Web依赖 -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency> <!--添加Mybatis依赖 -->
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.3.1</version>
            </dependency>
            <dependency><!--添加MySQL驱动依赖 -->
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency><!--添加Test依赖 -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
    

    2. 添加配置

    主要是配置数据源和开启Mybatis的自动驼峰映射

    @SpringBootApplication
    public class MybatisTransactionApplication {
    
        public static void main(String[] args) {
            //1.初始化
            SpringApplication application=  new SpringApplication(MybatisTransactionApplication.class);
    
            //2.添加数据源
            Map<String,Object> map = new HashMap<>();
            map.put("spring.datasource.url","jdbc:mysql://localhost:3306/socks?useSSL=false");
            map.put("spring.datasource.username","root");
            map.put("spring.datasource.password","root");
    
            //3.开启驼峰映射 (Such as account_id ==> accountId)
            map.put("mybatis.configuration.map-underscore-to-camel-case",true);
            application.setDefaultProperties(map);
    
            //4.启动应用
            application.run(args);
        }
    }
    
    

    3. 添加数据库记录

    打开 Navicat 的查询窗口,然后执行以下SQL:

    DROP TABLE IF EXISTS `account`;
    CREATE TABLE `account` (
      `account_id` varchar(30) ,
      `account_name` varchar(30),
      `balance` decimal(20,2),
      PRIMARY KEY (`account_id`)
    );
    
    insert into account values ('1','admin','1000.25');
    
    

    执行完毕后,可以查询到账户数据,如图:

    image

    4. 编写代码

    以操作账户金额为例,模拟正常操作金额提交事务,以及发生异常回滚事务。
    其中控制层代码如下:

    package com.hehe.controller;
    
    @RestController
    public class AccountController {
    
        @SuppressWarnings("all")
        @Autowired
        AccountService accountService;
    
        @GetMapping("/")
        public Account getAccount() {
            //查询账户
            return accountService.getAccount();
        }
    
        @GetMapping("/add")
        public Object addMoney() {
            try {
                accountService.addMoney();
            } catch (Exception e) {
                return "发生异常了:" + accountService.getAccount();
            }
            return getAccount();
        }
    }
    
    

    在业务层使用 @Transactional 开启事务,执行数据库操作后抛出异常。具体代码如下:

    package com.hehe.service;
    
    @Service
    public class AccountService {
    
        @SuppressWarnings("all")
        @Autowired
        AccountMapper accountMapper;
    
        public Account getAccount() {
            return accountMapper.getAccount();
        }
    
        @Transactional
        public void addMoney() throws Exception {
            //先增加余额
            accountMapper.addMoney();
            //然后遇到故障
            throw new RuntimeException("发生异常了..");
        }
    }
    
    

    数据库层就很简单了,我们通过注解来实现账户数据的查询,具体如下:

    package com.hehe.mapper;
    
    @Mapper
    public interface AccountMapper {
    
        @Select("select * from account where account_id=1")
        Account getAccount();
    
        @Update("update account set balance = balance+100 where account_id=1")
        void addMoney();
    }
    
    

    其中 Account 实体对象如下:

    package com.hehe.pojo;
    
    public class Account {
    
        private String accountId;
        private String accountName;
        private BigDecimal balance;
    
        // Override toString Method ..
        // Getter & Setters  ..
    }
    
    

    5. 测试事务

    启动应用,访问 http://localhost:8080 ,可以看到账户数据,如下:

    image

    然后访问 http://localhost:8080/add ,可以看到账户余额并没有增加,如下: 也就是说事务开启成功,数据得到回滚。

    image

    6. 常见坑点

    使用事务注解@Transactional 之前,应该先了解它的相关属性,避免在实际项目中踩中各种各样的坑点。

    常见坑点1:遇到检测异常时,事务默认不回滚。

    例如下面这段代码,账户余额依旧增加成功,并没有因为后面遇到SQLException(检测异常)而进行事务回滚!!

       @Transactional
        public void addMoney() throws Exception {
            //先增加余额
            accountMapper.addMoney();
            //然后遇到故障
            throw new SQLException("发生异常了..");
        }
    
    

    原因分析:因为Spring的默认的事务规则是遇到运行异常(RuntimeException及其子类)和程序错误(Error)才会进行事务回滚,显然SQLException并不属于这个范围。如果想针对检测异常进行事务回滚,可以在@Transactional 注解里使用
    rollbackFor 属性明确指定异常。例如下面这样,就可以正常回滚:

      @Transactional(rollbackFor = Exception.class)
        public void addMoney() throws Exception {
            //先增加余额
            accountMapper.addMoney();
            //然后遇到故障
            throw new SQLException("发生异常了..");
        }
    
    

    常见坑点2: 在业务层捕捉异常后,发现事务不生效。

    这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。例如:下面这段代码直接导致增加余额的事务回滚没有生效。

        @Transactional
        public void addMoney() throws Exception {
            //先增加余额
            accountMapper.addMoney();
            //谨慎:尽量不要在业务层捕捉异常并处理
            try {
                throw new SQLException("发生异常了..");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    

    不要小瞧了这些细节,往前暴露异常很大程度上很能够帮我们快速定位问题,而不是经常在项目上线后出现问题,却无法刨根知道哪里报错。

    推荐做法:若非实际业务要求,则在业务层统一抛出异常,然后在控制层统一处理。

        @Transactional
        public void addMoney() throws Exception {
            //先增加余额
            accountMapper.addMoney();
            //推荐:在业务层将异常抛出
            throw new RuntimeException("发生异常了..");
        }
    
    
    

    相关文章

      网友评论

          本文标题:Spring Boot使用事务

          本文链接:https://www.haomeiwen.com/subject/evdialtx.html