当前位置: 代码网 > it编程>编程语言>Java > SpringBoot 事务深度解析之从理论到实践的完整指南

SpringBoot 事务深度解析之从理论到实践的完整指南

2026年03月03日 Java 我要评论
前言在后端开发中,数据一致性是衡量系统可靠性的核心指标之一,而事务正是保障数据一致性的关键技术。无论是电商系统的订单支付、金融平台的资金流转,还是企业级应用的业务数据处理,都离不开事务的保驾护航。sp

前言

在后端开发中,数据一致性是衡量系统可靠性的核心指标之一,而事务正是保障数据一致性的关键技术。无论是电商系统的订单支付、金融平台的资金流转,还是企业级应用的业务数据处理,都离不开事务的保驾护航。springboot作为当前最流行的java开发框架,其对事务的支持简洁高效,极大降低了开发者的使用成本。本文将从事务的基础理论出发,逐步深入springboot事务的实现机制、使用方式、常见问题及优化方案,帮助开发者全面掌握springboot事务知识,轻松应对实际开发中的数据一致性挑战。

第一章 事务基础:你必须掌握的核心概念

1.1 什么是事务?

事务(transaction)是数据库操作的基本单元,它是由一组不可分割的数据库操作组成的逻辑集合。这组操作要么全部执行成功,要么全部执行失败,不存在部分执行的中间状态。例如,在电商下单场景中,需要完成“扣减库存”“创建订单”“扣减余额”三个操作,这三个操作就构成一个事务。如果其中任意一个操作失败,为了保证数据一致性,其他已执行的操作必须回滚,恢复到操作前的状态。

事务本质上是数据库提供的一种机制,主流的关系型数据库如mysql、oracle、postgresql等都原生支持事务。而springboot则是在数据库事务的基础上,通过spring框架的封装,为开发者提供了更便捷、更灵活的事务管理能力。

1.2 事务的acid特性

acid是事务的四大核心特性,是衡量事务机制是否可靠的标准,也是理解事务本质的关键。这四个特性相互关联,共同保障数据的一致性。

1.2.1 原子性(atomicity)

原子性是指事务中的所有操作是一个不可分割的整体,如同“原子”一样不可再分。事务的原子性要求:要么事务中的所有操作都执行成功并提交,要么所有操作都执行失败并回滚,不存在“部分成功、部分失败”的情况。

例如,银行转账业务中,“从a账户扣减1000元”和“向b账户增加1000元”是一个事务的两个操作。如果扣减操作成功但增加操作失败,原子性会保证扣减的1000元回滚到a账户,避免出现a账户资金减少但b账户资金未增加的错误数据。

1.2.2 一致性(consistency)

一致性是指事务执行前后,数据库中的数据必须处于一个合法、一致的状态,即数据需要满足业务规则和完整性约束。一致性是事务的最终目标,其他三个特性都是为了保障一致性而存在的。

以电商库存管理为例,假设某商品的初始库存为100件。如果同时有两个订单分别购买10件和20件商品,那么两个事务执行完成后,库存应该变为70件,这就是数据的一致性。如果由于事务机制缺陷,导致最终库存为80件或90件,就破坏了数据的一致性。

1.2.3 隔离性(isolation)

隔离性是指多个事务同时并发执行时,一个事务的执行过程不会被其他事务干扰,各个事务之间相互隔离,如同在独立的环境中执行一样。如果事务之间没有隔离性,多个并发事务操作同一批数据时,可能会出现脏读、不可重复读、幻读等问题,从而破坏数据一致性。

例如,事务a正在修改用户的余额,此时事务b读取该用户的余额。如果没有隔离性,事务b可能会读取到事务a修改过程中的中间数据,而这个数据最终可能因为事务a回滚而失效,导致事务b基于错误的数据进行业务处理。

1.2.4 持久性(durability)

持久性是指事务一旦执行成功并提交,其对数据库中数据的修改就是永久性的,即使后续发生数据库崩溃、服务器宕机等故障,已提交的事务数据也不会丢失。数据库通常通过将事务日志写入磁盘来实现持久性,当系统故障恢复时,可以通过事务日志恢复已提交的事务数据。

例如,用户完成订单支付后,事务提交成功,此时即使数据库服务器突然断电,再次启动后,用户的支付记录和订单状态也不会丢失,确保业务数据的可靠。

1.3 事务的并发问题

在多用户并发访问数据库的场景下,如果事务的隔离性得不到保障,就会出现一系列并发问题。根据问题的严重程度,主要分为以下四类:

1.3.1 脏读(dirty read)

脏读是指一个事务读取到了另一个事务尚未提交的修改数据。由于未提交的事务可能会回滚,因此读取到的数据是“脏”的,即无效数据。

场景示例:事务a执行“扣减用户余额100元”操作,但未提交;此时事务b读取该用户的余额,得到扣减后的金额;随后事务a因为异常回滚,用户余额恢复为原始值,但事务b已经基于读取到的脏数据进行了后续业务处理(如创建订单),导致业务错误。

1.3.2 不可重复读(non-repeatable read)

不可重复读是指在同一个事务中,多次读取同一批数据时,得到的结果不一致。这种问题通常是由于在两次读取之间,有其他事务修改并提交了该数据。

场景示例:事务a第一次读取用户余额为1000元;此时事务b修改该用户余额为800元并提交;事务a再次读取该用户余额时,得到的结果变为800元,与第一次读取的结果不一致,导致事务a的业务逻辑出现混乱。

1.3.3 幻读(phantom read)

幻读是指在同一个事务中,多次执行相同的查询语句时,查询结果的行数不一致。这种问题通常是由于在两次查询之间,有其他事务插入或删除了符合查询条件的数据。

场景示例:事务a执行“查询余额大于500元的用户”操作,得到10条记录;此时事务b插入了一条余额为600元的用户数据并提交;事务a再次执行相同的查询语句,得到11条记录,如同出现了“幻觉”一样。

1.3.4 丢失修改(lost update)

丢失修改是指两个事务同时修改同一数据,后提交的事务会覆盖先提交的事务的修改结果,导致先提交的事务的修改丢失。

场景示例:事务a读取用户余额为1000元,计划修改为900元;同时事务b也读取该用户余额为1000元,计划修改为800元;事务a先提交,将余额改为900元;随后事务b提交,将余额改为800元,导致事务a的修改被丢失。

1.4 数据库事务隔离级别

为了解决事务的并发问题,数据库定义了不同的事务隔离级别。隔离级别越高,事务的并发问题越少,但数据库的并发性能也会越低。开发者需要根据业务场景在数据一致性和并发性能之间进行权衡。sql标准定义了四个隔离级别,不同的数据库对这些隔离级别的支持程度有所不同。

1.4.1 读未提交(read uncommitted)

最低的隔离级别,允许一个事务读取另一个事务尚未提交的修改数据。该隔离级别无法解决脏读、不可重复读、幻读等任何并发问题,实际开发中很少使用。

1.4.2 读已提交(read committed)

允许一个事务读取另一个事务已经提交的修改数据。该隔离级别可以解决脏读问题,但无法解决不可重复读和幻读问题。这是大多数数据库的默认隔离级别,如oracle、sql server等。

1.4.3 可重复读(repeatable read)

保证同一个事务中,多次读取同一批数据时,得到的结果一致。该隔离级别可以解决脏读和不可重复读问题,但无法完全解决幻读问题(mysql的innodb引擎通过mvcc机制在可重复读级别下解决了幻读问题)。这是mysql的默认隔离级别。

1.4.4 串行化(serializable)

最高的隔离级别,要求所有事务串行执行,即一个事务执行完成后,另一个事务才能开始执行。该隔离级别可以解决所有并发问题,但会严重影响数据库的并发性能,适用于数据一致性要求极高但并发量极低的场景。

1.4.5 隔离级别与并发问题的关系

为了更清晰地展示隔离级别与并发问题的对应关系,以下表格进行了总结:

隔离级别脏读不可重复读幻读丢失修改
读未提交可能可能可能可能
读已提交不可能可能可能可能
可重复读不可能不可能mysql中不可能不可能
串行化不可能不可能不可能不可能

第二章 springboot事务的核心机制

2.1 spring事务管理的核心接口

spring框架的事务管理基于一系列核心接口实现,这些接口定义了事务管理的基本规范,springboot作为spring的子项目,完全继承了这些接口的功能,并通过自动配置简化了其使用。

2.1.1 platformtransactionmanager

platformtransactionmanager是spring事务管理的核心接口,它定义了事务的基本操作方法,如获取事务、提交事务、回滚事务等。该接口是一个抽象层,不同的数据库或持久层框架有其对应的实现类,例如:

  • datasourcetransactionmanager:基于jdbc数据源的事务管理器,适用于使用jdbc或mybatis进行数据访问的场景。
  • hibernatetransactionmanager:基于hibernate的事务管理器,适用于使用hibernate进行数据访问的场景。
  • jpatransactionmanager:基于jpa的事务管理器,适用于使用jpa进行数据访问的场景。

在springboot中,当引入spring-boot-starter-jdbc、spring-boot-starter-mybatis等依赖时,springboot会自动配置对应的platformtransactionmanager实现类,开发者无需手动配置。

2.1.2 transactiondefinition

transactiondefinition接口定义了事务的属性,如隔离级别、传播行为、超时时间、是否为只读事务等。这些属性决定了事务的执行规则,开发者可以通过配置这些属性来满足不同的业务需求。

2.1.3 transactionstatus

transactionstatus接口用于表示事务的当前状态,它提供了一系列方法来获取和修改事务的状态,如判断事务是否为新事务、是否已标记为回滚、设置事务回滚等。platformtransactionmanager在执行事务操作时,会通过该接口获取事务的状态信息,并根据状态执行相应的操作。

2.2 spring事务的传播行为

事务的传播行为是spring事务管理中一个非常重要的概念,它定义了当一个带有事务的方法调用另一个带有事务的方法时,如何处理两个方法之间的事务关系。例如,是沿用当前事务,还是创建一个新的事务,或是将当前事务挂起等。spring定义了7种事务传播行为,具体如下:

2.2.1 required(默认)

如果当前存在事务,则加入该事务;如果当前不存在事务,则创建一个新的事务。这是最常用的传播行为,适用于大多数业务场景。例如,servicea的methoda方法调用serviceb的methodb方法,如果methoda已经开启了事务,那么methodb会加入该事务;如果methoda没有开启事务,那么methodb会创建一个新的事务。

2.2.2 supports

如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务方式执行。该传播行为适用于那些不需要事务支持,但如果有事务也可以加入的方法。例如,一些查询方法,即使没有事务也可以执行,但若存在事务则可以保证数据的一致性。

2.2.3 mandatory

如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。该传播行为强制要求方法必须在事务中执行,适用于那些必须依赖事务才能保证数据一致性的方法。例如,资金转账的核心方法,必须在事务中执行,否则直接抛出异常。

2.2.4 requires_new

无论当前是否存在事务,都创建一个新的事务;如果当前存在事务,则将当前事务挂起。该传播行为适用于那些需要独立事务的方法,即使调用者存在事务,被调用者的事务也独立执行,不受调用者事务回滚的影响。例如,订单创建成功后记录日志的方法,即使订单创建事务回滚,日志记录事务也应该正常提交。

2.2.5 not_supported

以非事务方式执行;如果当前存在事务,则将当前事务挂起。该传播行为适用于那些不需要事务支持,且不希望干扰当前事务的方法。例如,一些耗时较长的非核心业务操作,如数据导出,以非事务方式执行可以提高性能,避免占用事务资源。

2.2.6 never

以非事务方式执行;如果当前存在事务,则抛出异常。该传播行为强制要求方法必须在非事务环境中执行,适用于那些绝对不能在事务中执行的方法。例如,某些初始化方法,如果在事务中执行可能会导致数据锁定。

2.2.7 nested

如果当前存在事务,则在当前事务中创建一个嵌套事务;如果当前不存在事务,则创建一个新的事务。嵌套事务是当前事务的一个子事务,它依赖于当前事务,只有当前事务提交后,嵌套事务才能提交;如果当前事务回滚,嵌套事务也会回滚,但嵌套事务的回滚不会影响当前事务的其他部分。该传播行为与requires_new的区别在于,requires_new创建的是完全独立的事务,而nested创建的是依赖于父事务的子事务。

2.3 springboot事务的实现方式

springboot支持两种事务管理方式:编程式事务和声明式事务。编程式事务需要开发者手动编写代码来控制事务的开启、提交和回滚,灵活性高但代码侵入性强;声明式事务通过注解或xml配置的方式实现事务管理,代码侵入性低,是springboot开发中推荐使用的方式。

2.3.1 编程式事务

编程式事务通过spring提供的transactiontemplate或直接使用platformtransactionmanager来实现。transactiontemplate是对编程式事务的封装,简化了事务操作的代码。

示例代码如下(基于transactiontemplate):

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;
import org.springframework.transaction.support.transactiontemplate;
@service
public class orderservice {
    @autowired
    private ordermapper ordermapper;
    @autowired
    private stockmapper stockmapper;
    @autowired
    private transactiontemplate transactiontemplate;
    public void createorder(order order) {
        // 使用transactiontemplate执行事务
        transactiontemplate.execute(status -> {
            try {
                // 扣减库存
                stockmapper.reducestock(order.getproductid(), order.getquantity());
                // 创建订单
                ordermapper.insertorder(order);
                return true;
            } catch (exception e) {
                // 事务回滚
                status.setrollbackonly();
                e.printstacktrace();
                return false;
            }
        });
    }
}

编程式事务的优点是可以精确控制事务的边界和执行逻辑,适用于复杂的事务场景;缺点是需要在业务代码中嵌入事务管理代码,增加了代码的耦合度。

2.3.2 声明式事务

声明式事务基于aop(面向切面编程)实现,通过注解或xml配置的方式将事务管理逻辑与业务逻辑分离,开发者只需在需要事务支持的方法上添加注解即可实现事务管理。springboot推荐使用@transactional注解实现声明式事务。

使用声明式事务的步骤非常简单:

  • 在springboot启动类上添加@enabletransactionmanagement注解(springboot 2.0+版本可省略该注解,因为其会自动识别事务相关依赖并开启事务管理)。
  • 在需要事务支持的service方法上添加@transactional注解。

示例代码如下:

import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;
import org.springframework.transaction.annotation.transactional;
@service
public class orderservice {
    @autowired
    private ordermapper ordermapper;
    @autowired
    private stockmapper stockmapper;
    // 添加@transactional注解开启事务
    @transactional
    public void createorder(order order) {
        // 扣减库存
        stockmapper.reducestock(order.getproductid(), order.getquantity());
        // 创建订单
        ordermapper.insertorder(order);
        // 如果出现异常,事务会自动回滚
        if (order.getquantity() <= 0) {
            throw new illegalargumentexception("订单数量不能小于等于0");
        }
    }
}

声明式事务的优点是代码侵入性低,事务管理逻辑与业务逻辑分离,提高了代码的可读性和可维护性;缺点是事务控制的粒度相对较粗,无法精确控制事务的执行逻辑。

第三章 springboot声明式事务的使用详解

3.1 @transactional注解的核心属性

@transactional注解提供了多个属性,用于配置事务的传播行为、隔离级别、超时时间等,开发者可以根据业务需求灵活配置。以下是常用的核心属性:

3.1.1 propagation:事务传播行为

用于指定事务的传播行为,默认值为propagation.required。可以通过propagation枚举类指定其他传播行为,例如:

@transactional(propagation = propagation.requires_new)
public void logordercreate(order order) {
    // 记录订单创建日志,该方法会创建新的事务
    logmapper.insertorderlog(order.getid(), "订单创建成功");
}

3.1.2 isolation:事务隔离级别

用于指定事务的隔离级别,默认值为isolation.default,即使用数据库的默认隔离级别。可以通过isolation枚举类指定其他隔离级别,例如:

@transactional(isolation = isolation.repeatable_read)
public list<order> queryorderbyuserid(long userid) {
    // 查询用户的订单,使用可重复读隔离级别
    return ordermapper.selectbyuserid(userid);
}

3.1.3 timeout:事务超时时间

用于指定事务的超时时间,单位为秒,默认值为-1,表示不设置超时时间(即使用数据库的默认超时时间)。如果事务执行时间超过指定的超时时间,事务会自动回滚。例如:

@transactional(timeout = 30)
public void batchimportdata(list<data> datalist) {
    // 批量导入数据,事务超时时间为30秒
    datamapper.batchinsert(datalist);
}

3.1.4 readonly:是否为只读事务

用于指定事务是否为只读事务,默认值为false。如果将该属性设置为true,数据库会对事务进行优化,提高查询性能,但此时事务中不能执行修改数据的操作(如插入、更新、删除),否则会抛出异常。该属性适用于纯查询的业务方法,例如:

@transactional(readonly = true)
public order queryorderbyid(long orderid) {
    // 纯查询方法,设置为只读事务
    return ordermapper.selectbyid(orderid);
}

3.1.5 rollbackfor:指定需要回滚的异常类型

默认情况下,spring事务只在遇到运行时异常(runtimeexception)和错误(error)时才会回滚事务,对于编译时异常不会回滚。通过rollbackfor属性可以指定需要回滚的异常类型,例如:

@transactional(rollbackfor = exception.class)
public void updateorderstatus(long orderid, integer status) throws exception {
    // 该方法抛出任何exception类型的异常,事务都会回滚
    order order = ordermapper.selectbyid(orderid);
    if (order == null) {
        throw new exception("订单不存在");
    }
    order.setstatus(status);
    ordermapper.update(order);
}

3.1.6 norollbackfor:指定不需要回滚的异常类型

与rollbackfor属性相反,norollbackfor属性用于指定不需要回滚的异常类型,即使该异常是运行时异常,事务也不会回滚。例如:

@transactional(norollbackfor = businessexception.class)
public void processorder(order order) {
    try {
        // 业务处理逻辑
    } catch (businessexception e) {
        // 抛出businessexception时,事务不回滚
        e.printstacktrace();
    }
}

3.2 @transactional注解的使用位置

@transactional注解可以用于类上或方法上,其作用范围有所不同:

3.2.1 用于类上

当@transactional注解用于类上时,该注解会作用于类中的所有公共(public)方法,即类中的所有public方法都会开启事务。如果类中的某个方法需要特殊的事务配置,可以在该方法上单独添加@transactional注解,方法上的注解会覆盖类上的注解配置。例如:

@service
@transactional(propagation = propagation.required, rollbackfor = exception.class)
public class orderservice {
    // 继承类上的事务配置
    public void createorder(order order) {
        // 业务逻辑
    }
    // 覆盖类上的事务配置,使用requires_new传播行为
    @transactional(propagation = propagation.requires_new)
    public void logordercreate(order order) {
        // 业务逻辑
    }
}

3.2.2 用于方法上

当@transactional注解用于方法上时,该注解只作用于当前方法。需要注意的是,@transactional注解只能作用于公共(public)方法,对于非public方法(如private、protected、default修饰的方法),注解不会生效,因为spring aop只能代理public方法。

3.3 事务的嵌套调用场景分析

在实际开发中,经常会遇到事务方法嵌套调用的场景,不同的传播行为会导致不同的事务执行结果。以下通过几个典型场景分析事务的嵌套调用逻辑:

3.3.1 场景一:同一service内的事务方法嵌套

在同一service类中,带有事务的methoda方法调用带有事务的methodb方法,此时由于spring aop的代理机制,methodb方法上的@transactional注解不会生效,事务的传播行为由methoda方法的配置决定。例如:

@service
public class orderservice {
    @transactional(propagation = propagation.required)
    public void methoda(order order) {
        // 调用同一类中的methodb方法
        methodb(order);
        // 业务逻辑
    }
    @transactional(propagation = propagation.requires_new)
    public void methodb(order order) {
        // 业务逻辑
    }
}

上述代码中,methoda调用methodb时,由于是内部方法调用,没有通过spring的代理对象,因此methodb上的@transactional注解不会生效,methodb会加入methoda的事务中,而不会创建新的事务。如果需要methodb的事务注解生效,可以通过以下两种方式解决:

  • 将methodb方法提取到另一个service类中,通过依赖注入的方式调用。
  • 在当前service类中通过@autowired注入自身的代理对象,使用代理对象调用methodb方法。

方式二的示例代码如下:

@service
public class orderservice {
    @autowired
    private orderservice orderservice; // 注入自身的代理对象
    @transactional(propagation = propagation.required)
    public void methoda(order order) {
        // 使用代理对象调用methodb方法
        orderservice.methodb(order);
        // 业务逻辑
    }
    @transactional(propagation = propagation.requires_new)
    public void methodb(order order) {
        // 业务逻辑
    }
}

3.3.2 场景二:不同service间的事务方法嵌套(required传播行为)

servicea的methoda方法(required传播行为)调用serviceb的methodb方法(required传播行为),此时methodb会加入methoda的事务中,两者共用一个事务。如果methoda或methodb中出现异常,整个事务会回滚。例如:

@service
public class servicea {
    @autowired
    private serviceb serviceb;
    @transactional(propagation = propagation.required)
    public void methoda() {
        // 业务逻辑a
        serviceb.methodb();
        // 如果此处抛出异常,methoda和methodb的操作都会回滚
        throw new runtimeexception("methoda异常");
    }
}
@service
public class serviceb {
    @transactional(propagation = propagation.required)
    public void methodb() {
        // 业务逻辑b
    }
}

上述代码中,methoda调用methodb后抛出异常,由于两者共用一个事务,因此methoda和methodb的操作都会回滚。

3.3.3 场景三:不同service间的事务方法嵌套(requires_new传播行为)

servicea的methoda方法(required传播行为)调用serviceb的methodb方法(requires_new传播行为),此时methodb会创建一个新的事务,与methoda的事务相互独立。如果methoda中出现异常,methoda的事务会回滚,但methodb的事务不会受影响;如果methodb中出现异常,methodb的事务会回滚,同时异常会传播到methoda,导致methoda的事务也回滚。例如:

@service
public class servicea {
    @autowired
    private serviceb serviceb;
    @transactional(propagation = propagation.required)
    public void methoda() {
        // 业务逻辑a
        serviceb.methodb();
        // 如果此处抛出异常,methoda的操作回滚,methodb的操作已提交不会回滚
        throw new runtimeexception("methoda异常");
    }
}
@service
public class serviceb {
    @transactional(propagation = propagation.requires_new)
    public void methodb() {
        // 业务逻辑b,执行完成后会提交事务
    }
}

上述代码中,methodb执行完成后会提交自己的事务,随后methoda抛出异常,methoda的事务回滚,但methodb的事务已经提交,因此methodb的操作不会回滚。

第四章 springboot事务的常见问题与解决方案

4.1 事务不生效的常见原因

在使用springboot事务的过程中,经常会遇到事务不生效的问题,即方法执行出现异常后,事务没有回滚。以下是事务不生效的常见原因及解决方案:

4.1.1 方法不是public修饰的

spring aop只能代理public方法,@transactional注解只对public方法生效。如果将注解添加到非public方法上,事务不会生效。

解决方案:将需要事务支持的方法改为public修饰。

4.1.2 异常类型不匹配

默认情况下,spring事务只在遇到runtimeexception和error时才会回滚,如果方法抛出的是编译时异常(如ioexception、sqlexception等),事务不会回滚。

解决方案:通过@transactional注解的rollbackfor属性指定需要回滚的异常类型,例如rollbackfor = exception.class。

4.1.3 异常被手动捕获且未重新抛出

如果在事务方法中手动捕获了异常,并且没有将异常重新抛出,spring无法感知到异常的发生,因此不会触发事务回滚。例如:

@transactional
public void createorder(order order) {
    try {
        stockmapper.reducestock(order.getproductid(), order.getquantity());
        ordermapper.insertorder(order);
    } catch (exception e) {
        // 捕获异常但未重新抛出,事务不会回滚
        e.printstacktrace();
    }
}

解决方案:在catch块中重新抛出异常,或者通过transactionstatus的setrollbackonly()方法手动标记事务回滚。修改后的代码如下:

@transactional
public void createorder(order order) {
    try {
        stockmapper.reducestock(order.getproductid(), order.getquantity());
        ordermapper.insertorder(order);
    } catch (exception e) {
        e.printstacktrace();
        // 重新抛出异常,触发事务回滚
        throw new runtimeexception("创建订单失败", e);
    }
}

4.1.4 同一service内的内部方法调用

如3.3.1节所述,同一service类中,事务方法调用另一个事务方法时,由于是内部方法调用,没有通过spring的代理对象,因此被调用方法上的@transactional注解不会生效。

解决方案:将被调用的事务方法提取到另一个service类中,或者在当前service类中注入自身的代理对象进行调用。

4.1.5 数据源未配置事务管理器

springboot需要为数据源配置对应的事务管理器(如datasourcetransactionmanager)才能实现事务管理。如果数据源没有配置事务管理器,事务不会生效。

解决方案:确保项目中引入了对应的数据源依赖(如spring-boot-starter-jdbc),springboot会自动配置事务管理器。如果是自定义数据源,需要手动配置事务管理器,示例代码如下:

import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.jdbc.datasource.datasourcetransactionmanager;
import org.springframework.transaction.platformtransactionmanager;
import javax.sql.datasource;
@configuration
public class transactionconfig {
    @bean
    public platformtransactionmanager transactionmanager(datasource datasource) {
        return new datasourcetransactionmanager(datasource);
    }
}

4.1.6 事务传播行为配置错误

如果事务传播行为配置不当,也可能导致事务不生效。例如,将传播行为配置为not_supported或never,会导致方法以非事务方式执行。

解决方案:根据业务场景选择合适的事务传播行为,大多数场景下使用默认的required即可。

4.2 事务回滚异常的处理技巧

在实际开发中,有时需要根据不同的业务场景灵活控制事务的回滚逻辑,以下是一些事务回滚异常的处理技巧:

4.2.1 手动标记事务回滚

如果需要在不抛出异常的情况下手动触发事务回滚,可以通过transactionstatus对象的setrollbackonly()方法实现。例如,在某些业务场景下,虽然没有发生异常,但根据业务规则需要回滚事务:

import org.springframework.transaction.annotation.transactional;
import org.springframework.transaction.support.transactionsynchronizationmanager;
import org.springframework.transaction.support.transactionstatus;
@service
public class orderservice {
    @transactional
    public void createorder(order order) {
        // 获取当前事务状态
        transactionstatus status = transactionsynchronizationmanager.getcurrenttransactionstatus();
        // 业务逻辑处理
        boolean issuccess = checkbusinessrule(order);
        if (!issuccess) {
            // 手动标记事务回滚
            status.setrollbackonly();
            return;
        }
        stockmapper.reducestock(order.getproductid(), order.getquantity());
        ordermapper.insertorder(order);
    }
    private boolean checkbusinessrule(order order) {
        // 业务规则校验
        return order.getamount() > 0;
    }
}

4.2.2 自定义异常与事务回滚

在实际开发中,通常会定义自定义的业务异常来表示不同的业务错误。为了让自定义异常能够触发事务回滚,需要在@transactional注解的rollbackfor属性中指定自定义异常类型,或者让自定义异常继承runtimeexception(因为spring默认对runtimeexception回滚)。

示例代码如下(自定义异常继承runtimeexception):

// 自定义业务异常
public class businessexception extends runtimeexception {
    public businessexception(string message) {
        super(message);
    }
}
@service
public class orderservice {
    @transactional
    public void createorder(order order) {
        if (order.getquantity() <= 0) {
            // 抛出自定义异常,触发事务回滚
            throw new businessexception("订单数量不能小于等于0");
        }
        stockmapper.reducestock(order.getproductid(), order.getquantity());
        ordermapper.insertorder(order);
    }
}

4.3 高并发场景下的事务优化

在高并发场景下,事务的使用不当可能会导致数据库性能下降、死锁等问题,以下是一些高并发场景下的事务优化技巧:

4.3.1 缩小事务范围

事务的执行时间越长,占用的数据库资源越多,并发性能越低,同时出现死锁的风险也越高。因此,应尽量缩小事务的范围,只将核心的数据库操作包含在事务中,避免在事务中执行耗时操作(如网络请求、文件io、复杂计算等)。

优化前的代码(事务中包含耗时操作):

@transactional
public void createorder(order order) {
    // 耗时的网络请求(不应包含在事务中)
    user user = userfeignclient.getuserbyid(order.getuserid());
    if (user == null) {
        throw new businessexception("用户不存在");
    }
    // 核心数据库操作
    stockmapper.reducestock(order.getproductid(), order.getquantity());
    ordermapper.insertorder(order);
}

优化后的代码(将耗时操作移出事务):

public void createorder(order order) {
    // 耗时的网络请求(移出事务)
    user user = userfeignclient.getuserbyid(order.getuserid());
    if (user == null) {
        throw new businessexception("用户不存在");
    }
    // 调用事务方法执行核心数据库操作
    docreateorder(order);
}
@transactional
public void docreateorder(order order) {
    // 核心数据库操作
    stockmapper.reducestock(order.getproductid(), order.getquantity());
    ordermapper.insertorder(order);
}

4.3.2 使用合适的隔离级别

隔离级别越高,数据一致性越好,但并发性能越低。在高并发场景下,应在保证数据一致性的前提下,尽量选择较低的隔离级别。例如,大多数业务场景下使用读已提交(read committed)隔离级别即可,既能避免脏读,又能保证较好的并发性能。

配置示例:

@transactional(isolation = isolation.read_committed)
public void createorder(order order) {
    // 业务逻辑
}

4.3.3 避免事务中的锁竞争

在高并发场景下,事务中的锁竞争是导致性能下降的主要原因之一。为了避免锁竞争,可以采取以下措施:

  • 尽量使用行锁,避免使用表锁。例如,在执行更新操作时,尽量通过主键或唯一索引定位数据,避免全表扫描导致的表锁。
  • 控制事务的执行顺序,避免不同事务交叉更新同一批数据导致死锁。例如,多个事务都需要更新a和b两条数据时,都按照先更新a再更新b的顺序执行。
  • 使用乐观锁替代悲观锁。乐观锁通过版本号或时间戳机制实现,不需要在事务中持有锁,适用于读多写少的高并发场景。

乐观锁示例(基于版本号):

// 实体类添加版本号字段
public class stock {
    private long id;
    private long productid;
    private integer quantity;
    private integer version; // 版本号字段
    // getter和setter
}
// mapper接口
public interface stockmapper {
    // 乐观锁更新:只有版本号匹配时才更新
    int reducestockwithversion(@param("productid") long productid, 
                               @param("quantity") integer quantity, 
                               @param("version") integer version);
}
// xml映射文件
<update id="reducestockwithversion">
    update stock 
    set quantity = quantity - #{quantity}, version = version + 1 
    where product_id = #{productid} and version = #{version}
</update>
// service方法
@transactional
public void reducestock(long productid, integer quantity) {
    // 查询库存和版本号
    stock stock = stockmapper.selectbyproductid(productid);
    if (stock == null || stock.getquantity() < quantity) {
        throw new businessexception("库存不足");
    }
    // 乐观锁更新
    int rows = stockmapper.reducestockwithversion(productid, quantity, stock.getversion());
    if (rows == 0) {
        // 更新失败,说明库存已被其他事务修改,抛出异常或重试
        throw new businessexception("库存更新失败,请重试");
    }
}

4.3.4 使用事务超时机制

在高并发场景下,部分事务可能由于资源竞争等原因导致执行时间过长,占用大量数据库资源。通过设置事务超时时间,可以让长时间未执行完成的事务自动回滚,释放数据库资源。

配置示例:

@transactional(timeout = 10) // 事务超时时间为10秒
public void batchprocessdata(list<data> datalist) {
    // 批量处理数据的业务逻辑
}

第五章 总结与拓展

5.1 文章知识点总结

本文围绕springboot事务展开,从基础理论到实践应用进行了全面讲解,核心知识点总结如下:事务基础层面,明确了事务的定义及acid四大核心特性,深入分析了脏读、不可重复读等并发问题及对应的数据库隔离级别,为后续springboot事务的学习奠定理论基础;核心机制层面,剖析了spring事务管理的三大核心接口,详细解读了7种事务传播行为的适用场景,对比了编程式与声明式两种事务实现方式的优劣;使用详解层面,聚焦声明式事务的@transactional注解,梳理了核心属性的配置方法、注解使用位置及嵌套调用场景;问题解决层面,归纳了事务不生效的常见原因及解决方案,提供了事务回滚异常的处理技巧,并给出高并发场景下的事务优化策略。通过理论与代码示例结合的方式,构建了完整的springboot事务知识体系。

5.2 知识拓展与延伸

除了本文讲解的核心内容,springboot事务还有一些进阶知识点值得关注。例如,分布式事务问题,在微服务架构中,跨服务的事务操作无法依赖单一数据库的事务机制,此时需要引入seata、saga等分布式事务解决方案;又如,事务同步机制,spring提供的transactionsynchronization接口可以在事务提交或回滚的不同阶段执行自定义逻辑,适用于事务完成后发送消息、更新缓存等场景。此外,springboot 3.x版本中对事务管理的自动配置进行了优化,结合jakarta ee的规范调整了相关api,开发者在升级框架时需注意适配。

5.3 推荐阅读资料

为帮助大家进一步深化对springboot事务及相关技术的理解,推荐以下优质资料:

  • 官方文档:spring framework官方文档的“transaction management”章节,是最权威的学习资料,详细阐述了spring事务的设计理念与实现细节

到此这篇关于springboot 事务深度解析之从理论到实践的完整指南的文章就介绍到这了,更多相关springboot 事务全解析内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

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

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com