SpringBoot 快速开启事务(附常见坑点)「建议收藏」

做一个积极的人编码、改bug、提升自己我有一个乐园,面向编程,春暖花开!序言:此前,我们主要通过XML配置Spring来托管事务。在SpringBoot则非常简单,只需在业务层添加事务注解(@Transactional )即可快速开启事务。虽然事务很简单,但对于数据方面是需要谨慎对待的,识别常见坑点对我们开发有帮助。1.引入依赖 <!–依赖管理 …

大家好,又见面了,我是全栈君。

 

做一个积极的人编码、改bug、提升自己

我有一个乐园,面向编程,春暖花开!

 

640?wx_fmt=png

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

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');

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

640?wx_fmt=png

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 &amp; Setters  ..	
}

5. 测试事务

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

640?wx_fmt=png

然后访问 http://localhost:8080/add ,可以看到账户余额并没有增加,如下:

640?wx_fmt=png

也就是说事务开启成功,数据得到回滚。

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("发生异常了..");	
}

 

》》END《《

 

 

推荐阅读

640?Spring中使用 @Transactional 注解配置事务管理详解

一文学会Java死锁和CPU 100% 问题的排查技巧

【面试】MySQL 中NULL和空值的区别?

Mybatis 批量插入引发的血案

Java内存管理-Stackoverflow问答-Java是传值还是传引用?(十一)

你有遇到过MySQL因大小写敏感导致的问题吗

MySQL 的COUNT(x)性能怎么样?

共勉:作为一名程序员你应该怎么提一个高质量的问题?

 

640?wx_fmt=png 图像 小部件
[640?Wx_fmt=png túxiàng xiǎo bùjiàn]

640? Wx_fmt = png image widget

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/121004.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • onedrive申请1T免费空间_onedrive如何升级空间

    onedrive申请1T免费空间_onedrive如何升级空间最近想要备份一下数据,但是硬盘备份容易坏不放心,百度网盘更恶心。听说Onedrive可以通过学生身份注册账号得到1T的免费空间,故尝试了一下,非常顺利。总结如下:账号注册网址:https://www.microsoft.com/zh-cn/education/products/office/default.aspx输入学生邮件地址正常注册即可查看剩余空间windows上的剩余空间查看出了问题:这里找到了一种更加通用的方法:https://zhida…

    2022年9月9日
    0
  • spring注解有哪些_Spring 注解

    spring注解有哪些_Spring 注解Spring注解@Configuration一.@Configuration的作用二.@Configuration的Spring容器启动方式三.不加@Configuration的@Bean的解

    2022年8月1日
    2
  • ubuntu18.04 linux journalctl 命令

    ubuntu18.04 linux journalctl 命令目录Help 输出所有的日志记录 匹配(match) 把日志保存到文件中 限定日志所能占用的最高容量 查看某次启动后的日志 查看指定时间段的日志 同时应用match和时间过滤条件 按unit过滤日志 通过日志级别进行过滤 实时更新日志 只显示最新的n行 控制输出 按可执行文件的路径过滤 查看内核日志 总结journalctl用来查询systemd…

    2022年5月24日
    60
  • Cadence License激活成功教程失败解决办法

    Cadence License激活成功教程失败解决办法问题1:UnabletorestartCadenceLicenseServerwiththenewlicensefile这个问题是在运行时可能遇到的。Cadence激活成功教程license,指定license文件时,提升下列的报错:UnabletorestartCadenceLicenseServer

    2022年7月26日
    7
  • MySQL——事务(Transaction)详解

    MySQL——事务(Transaction)详解该博客详解MySQL中的事务一、事务定义Transaction事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元)一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成事务只和DML语句有关,或者说DML语句才有事务。这个和业务逻辑有关,业务逻辑不同,DML语句的个数不同…

    2022年5月5日
    41
  • 链表排序总结(全)(C++)[通俗易懂]

    链表排序总结(全)(C++)[通俗易懂]文章目录链表排序与数组排序的区别借助外部空间冒泡排序插入排序归并排序快速排序链表排序与数组排序的区别数组的排序几乎所有人都很熟悉了,常用的算法插入、冒泡、归并以及快排等都会或多或少依赖于数组可以在O(1)时间随机访问的特点。链表排序一般指单链表排序,链表是不支持随机访问的,需要访问后面的节点只能从表头顺序遍历,所以链表的排序是一个相对比较复杂的问题。那么怎样进行链表排序呢?借助外部空间既然数组排序简单,那可以借助数组进行排序:把链表中的值一次遍历导入数组(时间复杂度O(n))对数组进行排序

    2022年10月11日
    0

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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