Spring 学习笔记之 @Transactional详解

一、数据库事务基础

数据库事务(Transaction)是数据库管理系统中用于确保数据一致性和完整性的一种机制。它是一组操作的集合,这些操作要么全部成功,要么全部失败,从而保证数据库状态的正确性。

1.1 事务的基本概念

  1. 定义

    事务是用户定义的一个操作序列,这些操作要么全部执行,要么全部不执行。它是数据库运行的基本单位。例如,在银行转账操作中,从一个账户扣除金额和向另一个账户增加金额必须同时成功或同时失败,这就需要通过事务来保证。

  2. 事务的生命周期

  • 开始事务:事务的执行开始,通常由用户或应用程序发起。

  • 执行事务:事务中的各个操作依次执行,如插入、更新、删除等。

  • 提交事务:如果事务中的所有操作都成功完成,事务被提交,所有操作对数据库的更改将永久生效。

  • 回滚事务:如果事务中的某个操作失败,事务将被回滚,所有已经执行的操作都会被撤销,数据库恢复到事务开始前的状态。

1.2 事务的特性(ACID)

事务的特性是通过 ACID 原则来保证的,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

1. 原子性(Atomicity)

  • 原子性是指事务中的所有操作要么全部成功,要么全部失败。事务是一个不可分割的最小执行单位。例如,在一个订单系统中,创建订单和扣款操作是一个事务。如果扣款成功但创建订单失败,那么整个事务会回滚,扣款操作也会被撤销,以保证系统的状态不会出现部分操作成功的情况。

2. 一致性(Consistency)

  • 一致性是指事务执行前后,数据库从一个一致的状态转换到另一个一致的状态。事务必须保证数据库的完整性约束没有被破坏。例如,在一个库存管理系统中,库存数量不能为负。如果一个事务试图将库存数量减少到负数,那么这个事务应该被回滚,以保证数据库的一致性。

3. 隔离性(Isolation)

  • 隔离性是指多个并发事务之间是相互隔离的,一个事务的执行不会受到其他事务的干扰。数据库系统提供了不同的隔离级别来控制事务之间的隔离程度。常见的隔离级别包括:

    • 读未提交(Read Uncommitted):最低的隔离级别,允许一个事务读取另一个事务未提交的数据。这种情况下可能会出现脏读(dirty read),即读取到其他事务未提交的错误数据。

    • 读已提交(Read Committed):一个事务只能读取到其他事务已经提交的数据,避免了脏读。但可能会出现不可重复读(non-repeatable read),即在同一个事务中,多次读取同一数据可能得到不同的结果。

    • 可重复读(Repeatable Read):保证在同一个事务中,多次读取同一数据的结果是一致的。但可能会出现幻读(phantom read),即在同一个事务中,查询满足某个条件的记录时,可能会出现新插入的记录。

    • 可串行化(Serializable):最高的隔离级别,事务之间完全隔离,按照串行的顺序执行,避免了脏读、不可重复读和幻读。但这种隔离级别会带来较大的性能开销。

4. 持久性(Durability)

  • 持久性是指事务一旦提交,其对数据库的更改将永久生效,即使系统发生故障也不会丢失。数据库系统通常通过日志(log)来保证持久性。当事务提交时,数据库会将事务的操作记录到日志中,即使系统崩溃,也可以通过日志恢复数据。

1.3 事务的并发控制

在多用户环境中,多个事务可能会同时对数据库进行操作,这就需要并发控制机制来保证事务的隔离性和一致性。常见的并发控制方法包括:

1. 锁机制

  • 共享锁(Shared Lock,S锁):当一个事务对数据加上共享锁后,其他事务可以读取该数据,但不能修改它。多个事务可以同时对同一数据加共享锁。

  • 排他锁(Exclusive Lock,X锁):当一个事务对数据加上排他锁后,其他事务不能对该数据加任何锁,即不能读取也不能修改。排他锁用于写操作,保证数据的独占访问。

  • 锁的粒度可以是行级锁、表级锁或数据库级锁。行级锁的粒度最小,锁的冲突概率较低,但管理开销较大;表级锁的粒度较大,锁的冲突概率较高,但管理开销较小。

2. 乐观锁和悲观锁

  • 悲观锁(Pessimistic Locking):假设冲突很可能会发生,因此在事务开始时就对数据加锁,直到事务结束才释放锁。悲观锁适用于写操作较多的场景,但可能会导致锁的冲突和等待。

  • 乐观锁(Optimistic Locking):假设冲突较少发生,因此在事务开始时不加锁,只有在提交时才检查是否有冲突。如果发现冲突,则回滚事务。乐观锁通常通过版本号(Version Number)或时间戳(Timestamp)来实现。乐观锁适用于读操作较多的场景,可以减少锁的开销。

1.4 事务的实现机制

数据库系统通过日志(log)和回滚段(rollback segment)等机制来实现事务的特性。

1. 日志(Log)

  • 日志记录了事务对数据库的所有操作,包括修改操作的前值和后值。当事务提交时,数据库会将日志写入磁盘,以保证持久性。如果系统发生故障,可以通过日志恢复数据。日志的写入顺序与事务的执行顺序一致,因此可以保证事务的原子性和持久性。

2. 回滚段(Rollback Segment)

  • 回滚段用于存储事务执行过程中数据的旧值。当事务回滚时,数据库可以从回滚段中恢复数据到事务开始前的状态。回滚段的大小和数量会影响事务的性能和并发能力。

1.5 事务的使用

在实际的数据库应用中,事务的使用通常由应用程序通过 SQL 语句来控制。

1. 显式事务

  • 显式事务是指用户明确地定义事务的开始和结束。例如,在 SQL 中可以使用以下语句:

    复制代码
    -- 开始事务
    BEGIN TRANSACTION;
    
    -- 执行事务中的操作
    INSERT INTO table_name (column1, column2) VALUES (value1, value2);
    UPDATE table_name SET column1 = value1 WHERE condition;
    
    -- 提交事务
    COMMIT;

    如果事务中的某个操作失败,可以通过以下语句回滚事务:

    复制代码
    ROLLBACK;

2. 隐式事务

  • 隐式事务是指数据库系统自动为每个单独的 SQL 语句启动一个事务。如果语句成功执行,则事务自动提交;如果语句失败,则事务自动回滚。隐式事务适用于简单的数据库操作,但对于复杂的业务逻辑,显式事务更能保证事务的完整性和一致性。

二、@Transactional介绍

@Transactional 是 Spring 中用于声明式事务管理的核心注解。它允许开发者通过简单的注解方式,将事务管理逻辑与业务逻辑分离,从而简化事务的管理。

2.1 作用

@Transactional 注解用于声明事务的边界。它可以让 Spring 容器在方法执行前后自动管理事务的开启、提交和回滚。具体来说:

  • 开启事务:在方法执行前,Spring 会创建一个新的事务(或加入已有的事务)。

  • 提交事务:如果方法正常执行完成,Spring 会提交事务。

  • 回滚事务:如果方法抛出异常,Spring 会根据配置决定是否回滚事务。

2.2 使用场景

@Transactional 通常用于服务层(@Service 注解的类)/ 数据库访问层(@Repository注解的类) 的方法上,因为事务管理通常与业务逻辑密切相关。例如:

复制代码
@Service
public class UserService {

    @Transactional
    public void updateUser(User user) {
        // 更新用户信息
        userRepository.save(user);
    }
}

2.3. 常见属性

@Transactional代码如下:

java 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    String[] label() default {};

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    String timeoutString() default "";

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

@Transactional 注解提供了多个属性,用于配置事务的行为。以下是一些常用的属性:

2.3.1 value/transactionManager

value和transactionManager属性可以用来指定使用的事务管理器。

value: 这个属性用于指定事务管理器的名称。当你的应用中配置了多个事务管理器时,可以通过value属性来指定使用哪一个。如果只有一个事务管理器,可以省略这个属性。

transactionManager: 这个属性与value属性的作用相同,都是用于指定事务管理器的名称。通常情况下,value和transactionManager可以互换使用,但transactionManager属性更明确地表达了其用途。

假设你的应用中配置了两个事务管理器,分别是transactionManagerA和transactionManagerB,你可以这样使用@Transactional注解:

java 复制代码
@Service
public class MyService {

    @Transactional("transactionManagerA")
    public void methodA() {
        // 使用 transactionManagerA
    }

    @Transactional(transactionManager = "transactionManagerB")
    public void methodB() {
        // 使用 transactionManagerB
    }
}

默认事务管理器: 如果没有指定value或transactionManager,Spring会使用默认的事务管理器。默认的事务管理器通常是第一个被定义的事务管理器。

事务管理器的配置: 确保在Spring配置中正确配置了事务管理器,并且名称与@Transactional注解中的指定名称一致。

通过合理使用value或transactionManager属性,可以灵活地控制不同方法或类使用不同的事务管理器,从而更好地管理事务。

2.3.2 propagation(事务传播行为)

在 Spring 中,@Transactional 注解的 propagation 属性用于定义事务传播行为,它决定了当一个事务方法被另一个事务方法调用时,事务应该如何处理。

后面我们描述外层的Transaction为父事务,内层被调用的事务为子事务。

  1. Propagation.REQUIRED
  • 描述:这是 @Transactional 注解的默认传播行为。如果当前存在事务,方法将加入该事务;如果当前没有事务,会创建一个新事务。

  • 示例场景:多个业务操作需要在同一个事务中完成,保证数据的一致性。比如在一个订单处理服务中,创建订单和扣减库存的操作需要在同一个事务里,若其中一个操作失败,整个事务回滚。

java 复制代码
@Service
public class OrderService {

    @Autowired
    private InventoryService inventoryService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder() {
        // 创建订单的业务逻辑
        inventoryService.reduceInventory();
        // 其他业务逻辑
    }
}

@Service
public class InventoryService {

    @Transactional(propagation = Propagation.REQUIRED)
    public void reduceInventory() {
        // 扣减库存的业务逻辑
    }
}

当调用 OrderServicecreateOrder 方法时,如果当前没有事务,会创建一个新事务。调用 InventoryServicereduceInventory 方法时,由于当前存在事务,reduceInventory 方法会加入到这个事务中。若在任何一个方法中出现异常,整个事务会回滚。

结论:

1)Propagation.REQUIRED 子事务任何一个失败回滚,所有事务都会回滚。

2)Propagation.REQUIRED 父事务失败回滚,所有子事务都会回滚。

  1. Propagation.NESTED
  • 描述:如果当前存在事务,在嵌套事务中执行;如果当前没有事务,和 REQUIRED 一样创建新事务。嵌套事务是当前事务的子事务,有自己的保存点。当嵌套事务回滚时,不会影响外部事务,但外部事务回滚时,嵌套事务也会回滚。

  • 示例场景:某些操作可以独立回滚,但又依赖于外部事务的上下文。例如在批量处理数据时,部分数据处理失败可以只回滚这部分操作,而不影响其他数据的处理。

  • 注意:需要数据库支持保存点(如 MySQL InnoDB、Oracle)。

java 复制代码
@Service
public class BatchService {

    @Autowired
    private SubBatchService subBatchService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void batchProcess() {
        try {
            subBatchService.subProcess();
        } catch (Exception e) {
            // 处理子批量处理异常
        }
        // 其他批量处理逻辑
    }
}

@Service
public class SubBatchService {

    @Transactional(propagation = Propagation.NESTED)
    public void subProcess() {
        // 子批量处理逻辑
        throw new RuntimeException("子批量处理异常");
    }
}

当调用 BatchServicebatchProcess 方法时会创建一个事务,调用 SubBatchServicesubProcess 方法时会创建一个嵌套事务。subProcess 抛出异常时,subProcess 中的操作会回滚,但 batchProcess 中的其他操作不受影响。

结论:

1)Propagation.NESTED 子事务失败回滚,不影响父事务的状态。

2)Propagation.NESTED 父事务失败回滚,所有子事务都会回滚。

  1. Propagation.REQUIRES_NEW
  • 描述:无论当前是否存在事务,都会创建一个新事务,并挂起当前事务(如果存在)。新事务和当前事务相互独立,一个事务的回滚或提交不会影响另一个事务。

  • 示例场景:当某个操作需要独立于外部事务时使用,比如记录日志操作,即使主业务事务失败,日志记录也应该保存。

java 复制代码
@Service
public class MainService {

    @Autowired
    private LogService logService;

    @Transactional(propagation = Propagation.REQUIRED)
    public void mainOperation() {
        try {
            logService.recordLog();
        } catch (Exception e) {
            // 处理日志记录异常
        }
        // 主业务逻辑
        throw new RuntimeException("主业务异常");
    }
}

@Service
public class LogService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void recordLog() {
        // 记录日志的业务逻辑
    }
}

调用 MainServicemainOperation 方法时会创建一个事务,调用 LogServicerecordLog 方法时会挂起 mainOperation 的事务,创建一个新事务。mainOperation 抛出异常时,主业务事务回滚,但日志记录事务不受影响。

结论:

1)Propagation.REQUIRES_NEW 子事务失败回滚,不影响父事务的状态。

2)Propagation.REQUIRES_NEW 父事务失败回滚,不影响所有子事务状态。

4.Propagation.SUPPORTS

  • 描述:如果当前存在事务,方法将加入该事务;如果当前没有事务,方法将以非事务方式执行。

  • 示例场景:某些查询操作可以选择在事务中执行以保证数据的一致性,也可以在非事务环境下执行以提高性能。

java 复制代码
@Service
public class QueryService {

    @Transactional(propagation = Propagation.SUPPORTS)
    public List<Product> getProducts() {
        // 查询产品列表的业务逻辑
        return null;
    }
}

如果调用 getProducts 方法时存在事务,它会加入该事务;如果不存在事务,它会以非事务方式执行。

  1. Propagation.NOT_SUPPORTED
  • 描述:方法将以非事务方式执行,如果当前存在事务,会挂起当前事务。

  • 示例场景:一些不需要事务管理的操作,如读取配置信息等,可以避免事务带来的开销。

java 复制代码
@Service
public class ConfigService {

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public String getConfig() {
        // 获取配置信息的业务逻辑
        return null;
    }
}

如果在事务环境中调用 getConfig 方法,当前事务会被挂起,getConfig 方法以非事务方式执行。

  1. Propagation.MANDATORY
  • 描述:如果当前存在事务,方法将加入该事务;如果当前没有事务,会抛出 IllegalTransactionStateException 异常。

  • 示例场景:确保方法必须在一个已存在的事务中执行,例如一些数据更新操作依赖于外部事务的上下文。

java 复制代码
@Service
public class UpdateService {

    @Transactional(propagation = Propagation.MANDATORY)
    public void updateData() {
        // 更新数据的业务逻辑
    }
}

如果在没有事务的情况下调用 updateData 方法,会抛出异常。

  1. Propagation.NEVER
  • 描述:方法以非事务方式执行,如果当前存在事务,会抛出 IllegalTransactionStateException 异常。

  • 示例场景:确保方法不应该在事务中执行,例如一些简单的计算操作。

java 复制代码
@Service
public class CalculationService {

    @Transactional(propagation = Propagation.NEVER)
    public int calculate(int a, int b) {
        return a + b;
    }
}

如果在事务环境中调用 calculate 方法,会抛出异常。

2.3.3 isolation(事务隔离级别)

指定事务的隔离级别,控制当前事务与其他事务之间的隔离程度。默认值为 Isolation.DEFAULT,即数据库默认的隔离级别。(即数据库隔离性里面的具体分类)

隔离级别 描述
DEFAULT 数据库默认的隔离级别。
READ_UNCOMMITTED 读未提交,允许并发事务读取未提交的数据(可能出现脏读)。
READ_COMMITTED 读已提交,允许并发事务读取已提交的数据。
REPEATABLE_READ 可重复读,保证在同一个事务中多次读取同一数据的结果是一致的。
SERIALIZABLE 串行化,最高级别的隔离,完全隔离并发事务。
2.3.4 timeout(事务超时时间)

指定事务的超时时间(以秒为单位)。如果事务执行时间超过指定值,事务将被回滚。默认值为 -1,表示使用数据库默认的超时时间。

2.3.5 readOnly(只读事务)

指定事务是否为只读事务。如果设置为 true,则事务不会修改数据,从而可以提高性能。默认值为 false

2.3.6 rollbackFor(回滚异常类)

指定哪些异常会导致事务回滚。默认情况下,Error子类和运行时异常(RuntimeException 及其子类)会导致事务回滚,而检查型异常不会导致事务回滚。可以通过该属性指定额外的异常类型。

2.3.7 noRollbackFor(不回滚异常类)

指定哪些异常不会导致事务回滚。即使这些异常是运行时异常,事务也不会回滚。

2.4. 使用方式

@Transactional 可以作用于类或方法上:

2.4.1 作用于方法
复制代码
@Service
public class UserService {
    @Transactional
    public void updateUser(User user) {
        // 更新用户信息
        userRepository.save(user);
    }
}
2.4.2 作用于类

如果将 @Transactional 作用于类上,则该类的所有方法都将默认使用相同的事务配置。

复制代码
@Transactional
@Service
public class UserService {
    public void updateUser(User user) {
        // 更新用户信息
        userRepository.save(user);
    }

    public void deleteUser(Long id) {
        // 删除用户
        userRepository.deleteById(id);
    }
}

2.5 @Transactional面试问题

1)@Transactional propagation REQUIRED/NETESD/REQUIRES_NEW的区别?

2)propagation = REQUIRED, 子事务失败的情况。看看下面代码的执行结果是什么?

java 复制代码
@Service
public class OrderService {
    ...

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void createOrder(Order order) {

        // Propagation.REQUIRED
        orderRepository.createOrder(order);

        try {
            // Propagation.REQUIRED
            inventoryRepository.deductStock(order.getProductId(), order.getQuantity());
        } catch (Exception e) {
            log.error("扣减库存失败", e);
        }

    }
}

@Repository
public class OrderRepository {
    ...

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void createOrder(Order order) {
        orderMapper.insert(order);
    }
}

@Repository
public class InventoryRepository {
    ...

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void deductStock(Long productId, Integer quantity) {
        inventoryMapper.deductStock(productId, quantity);
        throw new RuntimeException("出现未知错误");
    }

}

OrderRepository.createOrder, InventoryRepository.deductStock, OrderService.createOrder的事务有哪些能成功提交,为什么?

三个事务都不能成功提交,因为InventoyRepository.deductStock事务因为异常会回滚,导致外层事务失败,然后所有事务都会回滚。

详细日志如下:

2025-04-19T10:11:57.650+08:00 INFO 35024 --- [nio-8080-exec-1] com.example.controller.OrderController : 创建订单:Order(id=null, orderNumber=order-004, productId=1001, quantity=100, totalAmount=180, createdAt=2025-04-18T21:50:16)

Creating a new SqlSession

Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]

JDBC Connection [HikariProxyConnection@1303670389 wrapping com.mysql.cj.jdbc.ConnectionImpl@1e79d43] will be managed by Spring

==> Preparing: INSERT INTO orders (order_number, product_id, quantity, total_amount) VALUES (?, ?, ?, ?)

==> Parameters: order-004(String), 1001(Long), 100(Integer), 180(BigDecimal)

<== Updates: 1

Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]

Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868] from current transaction

==> Preparing: UPDATE inventory SET stock_quantity = stock_quantity - ? WHERE product_id = ?

==> Parameters: 100(Integer), 1001(Long)

<== Updates: 1

Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]

2025-04-19T10:11:57.754+08:00 ERROR 35024 --- [nio-8080-exec-1] com.example.service.OrderService : 扣减库存失败

java.lang.RuntimeException: 出现未知错误

at com.example.repository.InventoryRepository.deductStock(InventoryRepository.java:31) ~[classes/:na]

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]

at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]

at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.2.jar:6.2.2]

at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.2.2.jar:6.2.2]

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.2.2.jar:6.2.2]

at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) ~[spring-tx-6.2.0.jar:6.2.0]

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]

at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) ~[spring-tx-6.2.0.jar:6.2.0]

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.0.jar:6.2.0]

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) ~[spring-aop-6.2.2.jar:6.2.2]

at com.example.repository.InventoryRepository$$SpringCGLIB$$0.deductStock(<generated>) ~[classes/:na]

at com.example.service.OrderService.createOrder(OrderService.java:33) ~[classes/:na]

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]

at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]

at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]

at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]

at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.2.jar:6.2.2]

at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.2.2.jar:6.2.2]

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.2.2.jar:6.2.2]

at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) ~[spring-tx-6.2.0.jar:6.2.0]

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.0.jar:6.2.0]

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) ~[spring-aop-6.2.2.jar:6.2.2]

at com.example.service.OrderService$$SpringCGLIB$$0.createOrder(<generated>) ~[classes/:na]

at com.example.controller.OrderController.createOrder(OrderController.java:21) ~[classes/:na]

Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]

Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@e59868]

2025-04-19T10:11:57.773+08:00 ERROR 35024 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only] with root cause

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:938) ~[spring-tx-6.2.0.jar:6.2.0]

at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:754) ~[spring-tx-6.2.0.jar:6.2.0]

at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:698) ~[spring-tx-6.2.0.jar:6.2.0]

at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:416) ~[spring-tx-6.2.0.jar:6.2.0]

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.0.jar:6.2.0]

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.2.jar:6.2.2]

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:727) ~[spring-aop-6.2.2.jar:6.2.2]

at com.example.service.OrderService$$SpringCGLIB$$0.createOrder(<generated>) ~[classes/:na]

at com.example.controller.OrderController.createOrder(OrderController.java:21) ~[classes/:na]

3)progation = NESTED, 子事务失败的情况。以下代码的执行结果?

java 复制代码
@Service
public class OrderService {
    ...

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void createOrder(Order order) {

        // Propagation.NESTED
        orderRepository.createOrder(order);

        try {
            // Propagation.NESTED
            inventoryRepository.deductStock(order.getProductId(), order.getQuantity());
        } catch (Exception e) {
            log.error("扣减库存失败", e);
        }

    }
}

@Repository
public class OrderRepository {
    ...

    @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void createOrder(Order order) {
        orderMapper.insert(order);
    }
}

@Repository
public class InventoryRepository {
    ...

    @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void deductStock(Long productId, Integer quantity) {
        inventoryMapper.deductStock(productId, quantity);
        throw new RuntimeException("出现未知错误");
    }

}

OrderRepository.createOrder, InventoryRepository.deductStock, OrderService.createOrder的事务有哪些能成功提交,为什么?

OrderRepository.createOrder 成功提交

InventoryRepository.deductStock 失败回滚

OrderService.createOrder 成功提交

因为NESTED标记的事务失败不会影响外层事务的结果。

参考文档:
Transaction Propagation :: Spring Framework

相关推荐
NovakG_5 分钟前
SpringCloud小白入门+项目搭建
后端·spring·spring cloud
带刺的坐椅3 小时前
能用 Java8 开发 MCP(或 MCP Server),这才是 MCP 自由(Solon AI MCP)!
java·spring·ai·solon·mcp·mcp-server
Java个体户4 小时前
struts2和springmvc混合项目,上传文件报错
spring
懒虫虫~5 小时前
Spring源码中关于抽象方法且是个空实现这样设计的思考
java·后端·spring
Minyy118 小时前
使用注解方式整合ssm时,启动tomcat扫描不到resource下面的xxxmapper.xml问题,解决方法
java·spring boot·spring·apache·mybatis
葵续浅笑9 小时前
Spring之我见 - Spring Boot Starter 自动装配原理
java·spring boot·spring·自动装配
带刺的坐椅11 小时前
MCP Server Java 开发框架的体验比较(spring ai mcp 和 solon ai mcp)
java·spring·ai·solon·mcp-server
西岭千秋雪_12 小时前
Nacos配置中心客户端处理服务端配置信息源码解析
java·开发语言·分布式·spring·微服务·中间件
A阳俊yi13 小时前
整合SSM——(SpringMVC+Spring+Mybatis)
java·spring·mybatis