详解Springboot事务管理

关于"详解Springboot事务管理"的攻略,我可以给出以下的完整解析:

什么是事务管理

事务(Transaction)是指作为一个不可分割的工作单位所需要执行的一系列操作,这些操作要么全部都执行成功,要么全部都执行失败。对于一些需要多步操作的业务中,我们需要保证其中的每一步都可以正确执行,并且在其中任何一步出错的情况下,都可以撤回所有操作以保证数据的一致性。

事务管理就是针对这种业务场景,进行的一种统一的事务处理机制,通过某种方式,能够确保整个事务的原子性、一致性以及隔离性和持久性。

在Spring框架中,为了支持事务,提供了一种事务操作的管理机制。当我们配置了Spring的事务管理后,在开启事务的情况下,Spring会自动创建一个事务管理器,并为每一个事务绑定一个单独的数据库连接。在操作数据库过程中,如果其中有任何一步执行失败,整个事务都会回滚,确保数据的一致性。

好了,了解了什么是事务管理,接下来我们来看看如何在Springboot中进行配置,实现一个基本的事务案例。

Springboot事务管理配置

在Springboot应用的pom文件中,需要导入以下的对应的依赖项

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--添加事务支持-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!--添加MySQL支持-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

配置文件:application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&autoReconnect=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
logging.level.org.hibernate.SQL=debug
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace

这里我们配置了连接到本地 mysql 数据库,在 application.properties 文件中,指定了数据库连接信息和用户名密码。并且为了使JPA与数据库表进行同步,配置了JPA的 hibernate.ddl-auto 属性。

除此之外,在依赖项中,需要添加 spring-boot-starter-data-jpa , 它提供了数据库操作的事务支持。

示例一:转账处理

考虑这样一个业务场景,一个应用中涉及到了转账操作。其中涉及到了两个数据表, account 表表示账户信息,内含列 id、name 和 balance;transfer_log 表表示转账流程信息,是一个记录转账历史的表,内含列 id、out_account、in_account 和 money 。那么要在不同转账户之间进行转账的操作,我们肯定需要管理它的事务,即将它的转账操作封装到一个事务中,保证在转账出现异常时也能保证数据的一致性。

我们先看一下account表的定义,这里只列出了关键字段,数据表结构如下

CREATE TABLE `account` (
    `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '自动编号',
    `name` VARCHAR(50) DEFAULT NULL COMMENT '名称',
    `balance` DECIMAL(20,2) DEFAULT NULL COMMENT '余额',
    PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

定义transfer_log表的定义,数据表结构如下

CREATE TABLE `transfer_log` (
    `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '自动编号',
    `out_account` INT(11) NOT NULL COMMENT '转出账户',
    `in_account` INT(11) NOT NULL COMMENT '转入账户',
    `money` DECIMAL(10,2) NOT NULL COMMENT '转账金额',
    PRIMARY KEY (`id`),
) ENGINE=INNODB DEFAULT CHARSET=utf8;

上述代码中,transfer_log中的 out_account、in_account 字段需要赋值账户表account中的id,即关联查询。

针对转账场景下的事务管理,下面我们提供一个演示程序:

@Service
public class TransferService {

    @Autowired
    private AccountRepository accountRepository;

    @Autowired
    private TransferLogRepository transferLogRepository;

    /**
     * 转账
     * @param fromUser
     * @param toUser
     * @param money
     * @throws ServiceException 转账异常
     */
    @Transactional(rollbackFor = ServiceException.class)
    public void transfer(String fromUser, String toUser, Double money) throws ServiceException {
        Account fromAccount = accountRepository.findByUsername(fromUser);
        Account toAccount = accountRepository.findByUsername(toUser);
        if(fromAccount.getBalance() < money){
            throw new ServiceException("账户余额不足");
        }
        fromAccount.setBalance(fromAccount.getBalance() - money);
        toAccount.setBalance(toAccount.getBalance() + money);
        accountRepository.save(fromAccount);
        accountRepository.save(toAccount);

        TransferLog log = new TransferLog();
        log.setOutAccount(fromAccount.getId());
        log.setInAccount(toAccount.getId());
        log.setMoney(money);
        transferLogRepository.save(log);
    }
}

这里具体说明一下。

首先将 AccountRepository 和 TransferLogRepository 作为依赖项,因为涉及到数据库操作,我们需要配置数据的查询和保存方式。这两个类分别是用来对应account和transfer_log两张表的。

再看以下@Transactional注解,这个注释在这里保证了整个转账操作的执行序列,如果其中任何一步发生了错误,那么整个过程将自动回滚,每步的操作都将撤回,以确保数据的一致性。

在进行转账操作时,需要查询出相关账户信息,若出现转账失败的情况,则通过throw new ServiceException(...) 抛出异常,会被上面@Transactional中的rollbackFor = ServiceException.class截获。并将异常信息记录到日志中。

在转账操作都执行完毕之后,我们会在transfer_log表中,增加一条记录,以便于我们后续的查询。

No.2也是我们在实际业务场景中常常遇到的需求:同时对多张表进行事务管理。接下来我们快速了解一下这个业务需求并提供代码示例。

示例2:订单以及订单详情记录

在实际的业务场景中,经常涉及到多张表之间的联动操作、数据交互,如下这辑种情况:

  • 创建一张订单表 order_info,记录订单基础信息
  • 创建一张订单详情表 detail_info,记录订单明细信息
  • 在对应订单明细页面中,用户可以添加多条明细记录,每条记录都要持久化到detail_info表中并且记录到order_info表中。

为了高效地完成这一系列操作我们需要使用到 Springboot的事务管理机制来确保数据的一致性、隔离性和原子性,确保当任何一步操作失败时,被整个事务自动回滚。

代码实现中,我们通过新增一个 order_info 的记录,并将其生成的主键作为 detail_info 中的外键关联起来,我们来看以下两个 Repository 的代码。

order_info 表:

CREATE TABLE `order_info` (
    `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '自动编号',
    `price` DECIMAL(10,2) NOT NULL COMMENT '订单总价',
    PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

detail_info 表:

CREATE TABLE `detail_info` (
    `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '自动编号',
    `order_id` INT(11) NOT NULL COMMENT '对应订单编号',
    `detail` CHAR(20) NOT NULL COMMENT '订单详情',
    PRIMARY KEY (`id`),
    KEY `fk_order_id` (`order_id`),
    CONSTRAINT `fk_order_id` FOREIGN KEY (`order_id`) REFERENCES `order_info` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

上面的代码中,detail_info的 order_id 字段和order_info表的主键相连。主键[id]与外键[order_id]形成了主外键约束。

使用Springboot事务管理机制,我们只需要通过@Transactional注释,简单地对方法进行标记,就可以确保整个过程的原子性。

我们看一下订单处理服务类:

@Service
public class OrderService {

    @Autowired
    OrderRepository orderRepository;

    @Autowired
    DetailRepository detailRepository;

    @Transactional(rollbackFor = ServiceException.class)
    public void saveOrder(OrderInfo order, List<DetailInfo> detailList) throws ServiceException {
        order = orderRepository.save(order);
        int orderId = order.getId();
        Iterator ftr = detailList.iterator();

        while(ftr.hasNext()) {
            DetailInfo detail = (DetailInfo)ftr.next();
            detail.setOrderId(orderId);
            detailRepository.save(detail);
        }

    }
}

这里,我们注入 OrderRepository 和 DetailRepository ,并且使用 @Transactional 这个注释,对整个方法进行事务支持。在操作的过程中,保证业务的完整性,同时也消除了中间出现的任何问题。

至此,我们已经对 “详解Springboot事务管理”的内容进行了完整的讲解。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解Springboot事务管理 - Python技术站

(0)
上一篇 2023年5月15日
下一篇 2023年5月15日

相关文章

  • Linux 下java jps命令使用解析详解

    Linux 下 java jps 命令使用解析详解 Java 程序在运行的时候,如果需要查看当前 Java 进程,可以使用 jps 命令。本文通过详细介绍各个参数以及示例,帮助用户更好地使用 jps 命令。 为什么要使用 jps 命令 jps 命令用于查看当前 Java 进程的进程 ID (PID) 以及启动类的类名 (fully qualified nam…

    Java 2023年5月26日
    00
  • Spring Boot2开发之Spring Boot整合Shiro两种详细方法

    SpringBoot2开发之SpringBoot整合Shiro两种详细方法 Shiro是一个强大且易于使用的Java安全框架,可以提供身份验证、授权、加密和会话管理等功能。本文将详细讲解如何在Spring Boot应用程序中整合Shiro,包括两种详细方法。 方法一:使用Shiro Spring Boot Starter Shiro Spring Boot …

    Java 2023年5月15日
    00
  • Kotlin 标准函数和静态方法示例详解

    这是一篇关于 Kotlin 标准函数和静态方法的详细攻略,本文将会介绍 Kotlin 标准函数和静态方法的相关概念、使用方法以及示例说明。包含以下几个部分: Kotlin 标准函数和静态方法的概念介绍 Kotlin 标准函数示例说明 Kotlin 静态方法示例说明 Kotlin 标准函数和静态方法的概念介绍 Kotlin 标准函数的概念 Kotlin 标准函…

    Java 2023年5月26日
    00
  • 基于SSM 集成 Freemarker模板引擎的方法

    基于SSM集成Freemarker模板引擎的方法主要分为以下三步: 1. 导入Freemarker相关依赖包 在pom.xml文件中,我们需要导入Freemarker的依赖包。具体代码如下: <!– Freemarker 引擎 –> <dependency> <groupId>org.freemarker</gr…

    Java 2023年5月31日
    00
  • JAVA生成pdf文件的实操教程

    JAVA生成PDF文件的实操教程 本教程将教你如何使用JAVA生成PDF文件。你将学会使用开源库iText,它是一个功能强大的PDF库,支持PDF文件的创建、文本、表格、图片、字体等元素的操作。 步骤1:导入iText库 你需要先下载iText库并导入到你的JAVA项目中。可以从官网或Github获取。使用maven管理,可以这样引入: <depend…

    Java 2023年5月19日
    00
  • Java自定义长度可变数组的操作

    下面就给您讲解一下Java自定义长度可变数组的操作的完整攻略。 概述 在Java语言中,数组是一组相同数据类型元素的集合。创建数组时需要指定数组的长度,一旦数组长度被确定,就无法改变。但是在实际开发中,有一些场景需要使用可变长度的数组,这是怎么实现的呢? 实现原理 Java提供了List接口来实现可变长度的数组,List接口的实现类多种多样,常用的有Arra…

    Java 2023年5月26日
    00
  • mybatis 实体类字段大小写问题 字段获取不到值的解决

    问题背景:在使用 MyBatis 进行数据查询时,有时会遇到实体类字段大小写问题,导致查询结果为空,需要解决该问题。 解决思路:针对实体类字段大小写问题,我们可以使用 MyBatis 提供的一些功能进行解决,包括在 SQL 映射文件中配置 resultMap、使用@Result注解或通过配置全局配置文件等方法。 具体步骤如下: 配置resultMap 在 S…

    Java 2023年5月26日
    00
  • Java spring的三种注入方式详解流程

    Java Spring的三种注入方式详解流程 在Java Spring中,有三种常用的依赖注入方式:构造函数注入、Setter方法注入以及字段注入。下面将分别给出这三种方式的详细讲解流程。 构造函数注入 步骤一:定义一个接口 首先,我们需要定义一个接口。这个接口将会被一个实现类所实现。在这个接口中,我们可以定义一些方法,让实现类去具体实现这些方法。 publ…

    Java 2023年5月19日
    00
合作推广
合作推广
分享本页
返回顶部