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 {@Transactionalpublic void updateUser(User user) {// 更新用户信息userRepository.save(user);}
}

2.3. 常见属性

@Transactional代码如下:

@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注解:

@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 注解的默认传播行为。如果当前存在事务,方法将加入该事务;如果当前没有事务,会创建一个新事务。

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

@Service
public class OrderService {@Autowiredprivate InventoryService inventoryService;@Transactional(propagation = Propagation.REQUIRED)public void createOrder() {// 创建订单的业务逻辑inventoryService.reduceInventory();// 其他业务逻辑}
}@Service
public class InventoryService {@Transactional(propagation = Propagation.REQUIRED)public void reduceInventory() {// 扣减库存的业务逻辑}
}

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

结论:

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

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

2. Propagation.NESTED

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

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

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

@Service
public class BatchService {@Autowiredprivate 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("子批量处理异常");}
}

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

结论:

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

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

3. Propagation.REQUIRES_NEW

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

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

@Service
public class MainService {@Autowiredprivate 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() {// 记录日志的业务逻辑}
}

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

结论:

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

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

4.Propagation.SUPPORTS

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

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

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

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

5. Propagation.NOT_SUPPORTED

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

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

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

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

6. Propagation.MANDATORY

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

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

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

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

7. Propagation.NEVER

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

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

@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 {@Transactionalpublic 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, 子事务失败的情况。看看下面代码的执行结果是什么?
 

@Service
public class OrderService {...@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void createOrder(Order order) {// Propagation.REQUIREDorderRepository.createOrder(order);try {// Propagation.REQUIREDinventoryRepository.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, 子事务失败的情况。以下代码的执行结果?

@Service
public class OrderService {...@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)public void createOrder(Order order) {// Propagation.NESTEDorderRepository.createOrder(order);try {// Propagation.NESTEDinventoryRepository.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 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/76414.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Openlayers】Openlayers 入门教程

Openlayers 入门教程 -系列文章列表 openlayers 入门教程&#xff08;一&#xff09;&#xff1a;openlayers简介 openlayers 入门教程&#xff08;二&#xff09;&#xff1a;Map 篇 openlayers 入门教程&#xff08;三&#xff09;&#xff1a;View 篇 openlayers 入门教程&a…

【Lua语言】Lua语言快速入门

初始Lua Lua是一种轻量小巧的脚本语言&#xff0c;他使用标准C语言编写并以源代码形式开放。这意味着Lua虚拟机可以很方便的嵌入别的程序中&#xff0c;从而为应用程序提供灵活的扩展和定制功能。同时&#xff0c;在目前脚本引擎中&#xff0c;Lua的运行速度占有绝对优势。 变…

车载诊断新架构--- SOVD初入门(上)

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 周末洗了一个澡,换了一身衣服,出了门却不知道去哪儿,不知道去找谁,漫无目的走着,大概这就是成年人最深的孤独吧! 旧人不知我近况,新人不知我过…

linux查看目录相关命令

查看目录命令 学习目标 能够使用Linux命令查看目录信息 1. 查看目录命令的使用 命令说明ls查看当前目录信息tree以树状方式显示目录信息 ls命令效果图: tree命令效果图: 2. 查看当前目录路径 命令说明pwd查看当前目录路径 pwd命令效果图: 3. 清除终端内容 命令说明clear…

JavaScript中的Event事件对象详解

一、事件对象&#xff08;Event&#xff09;概述 1. 事件对象的定义 event 对象是浏览器自动生成的对象&#xff0c;当用户与页面进行交互时&#xff08;如点击、键盘输入、鼠标移动等&#xff09;&#xff0c;事件触发时就会自动传递给事件处理函数。event 对象包含了与事件…

OSPF综合实验(HCIP)

1&#xff0c;R5为ISP&#xff0c;其上只能配置Ip地址&#xff1b;R4作为企业边界路由器&#xff0c; 出口公网地址需要通过ppp协议获取&#xff0c;并进行chap认证 2&#xff0c;整个OSPF环境IP基于172.16.0.0/16划分&#xff1b; 3&#xff0c;所有设备均可访问R5的环回&…

2024-04-19| Java: Documented注解学习 JavaDoc

在 Java 中&#xff0c;Documented 是一个元注解&#xff08;meta-annotation&#xff09;&#xff0c;用于标记其他注解&#xff0c;表明这些注解应该被包含在 JavaDoc 文档中。以下是关于 Documented 注解的作用的简要说明&#xff1a; 作用 记录注解信息到 JavaDoc&#x…

15.Chromium指纹浏览器开发教程之WebAudio指纹定制

WebAudio指纹概述 浏览器中的 WebAudio API 提供了丰富的功能&#xff0c;其中包括了大量生成和处理音频数据的API。WebAudio API 的音频指纹技术是一种利用音频信号的特征来唯一标识音频的技术。因为WebAudio API 提供了丰富的音频处理功能&#xff0c;包括合成、过滤、分析等…

2025年赣教云智慧作业微课PPT模板

江西的老师们注意&#xff0c;2025年赣教云智慧作业微课PPT模版和往年不一样&#xff0c;千万不要搞错了&#xff0c;图上的才是正确的2025年的赣教云智慧作业微课PPT模版&#xff0c;赣教云智慧作业官网有问题&#xff0c;无法正确下载该模板&#xff0c;需要该模板的&#xf…

2.5.1DOS下常用工具 curl,netstat,telnet命令使用

curl命令 Win10及以上系统默认已安装Curl&#xff0c;打开命令提示符输入 curl --help&#xff0c;若显示帮助信息则无需安装 ​​手动安装方法​​ 官网下载&#xff1a;访问 curl官网 选择Windows版本curl for Windows若需在 Windows XP 等旧系统使用&#xff0c;需选择更…

使用Redis实现实时排行榜

为了实现一个实时排行榜系统&#xff0c;我们可以使用Redis的有序集合&#xff08;ZSet&#xff09;&#xff0c;其底层通常是使用跳跃表实现的。有序集合允许我们按照分数&#xff08;score&#xff09;对成员&#xff08;member&#xff09;进行排序&#xff0c;因此非常适合…

Linux——firewalld防火墙(笔记)

目录 一&#xff1a;Firewalld防火墙的概述 &#xff08;1&#xff09;firewalld简介 &#xff08;2&#xff09;firewalld&iptables的关系 &#xff08;3&#xff09;firewalld与iptables service的区别 1. ‌规则管理方式‌ 2. ‌默认策略与设计逻辑‌ 3. ‌配置文…

JS中实现类似sleep、wait、delay的延时功能

前言 编写代码时很多时候需要进行流程化的操作&#xff0c;各个流程间通常需要等待一定时间&#xff0c;这在很多语言中通常可以使用 sleep 、 wait 、 delay 等函数来实现。JavaScript原生并没有类似的功能&#xff0c;想要延时通常就是使用 setTimeout(functionRef, delay) …

Elasticsearch:使用 ES|QL 进行搜索和过滤

本教程展示了 ES|QL 语法的示例。请参考 Query DSL 版本&#xff0c;以获得等效的 Query DSL 语法示例。 这是一个使用 ES|QL 进行全文搜索和语义搜索基础知识的实践介绍。 有关 ES|QL 中所有搜索功能的概述&#xff0c;请参考《使用 ES|QL 进行搜索》。 在这个场景中&#x…

Java 动态代理实现

Java 动态代理实现 一、JDK动态代理二、CGLIB动态代理三、动态代理的应用场景四、JDK代理与CGLIB代理比较 动态代理是Java中一种强大的技术&#xff0c;它允许在运行时创建代理对象&#xff0c;用于拦截对目标对象的方法调用。 一、JDK动态代理 JDK动态代理是Java标准库提供的代…

Apache IoTDB V2.0.2/V1.3.4 发布|新增表模型权限管理、UDF、嵌套查询功能

Release Announcement Version 2.0.2/1.3.4 Apache IoTDB V2.0.2、V1.3.4 已经发布&#xff01; V2.0.2 作为树表双模型正式版本&#xff0c;主要新增表模型权限管理、用户管理以及相关操作鉴权&#xff0c;并新增了表模型 UDF、系统表和嵌套查询等功能。 V1.3.4 主要新增模式…

鸿蒙开发11-ARKUI框架

ARKUI&#xff08;方舟 UI 框架&#xff09;是 HarmonyOS Next&#xff08;原 OpenHarmony&#xff09;的核心 UI 开发框架&#xff0c;基于声明式编程范式&#xff0c;支持 ArkTS 语言&#xff0c;能够高效构建跨设备的响应式应用。以下是对 ARKUI 框架及开发的详细介绍&#…

Linux 进程间通信详解

一.进程间通信介绍 1. 进程间通信概念 进程间通信&#xff08;Inter-Process Communication, IPC&#xff09;是指在不同进程之间传递或交换信息的一种机制。在操作系统中&#xff0c;进程是资源分配和独立运行的基本单位&#xff0c;它们拥有各自独立的内存空间和系统资源。…

从0开始掌握动态规划

动态规划的核心思想 -- 以空间换时间 复杂点说通过分解问题为子问题并存储子问题解来优化复杂计算的算法策略。 简单看个问题。 一&#xff0c;初始&#xff1a;求最长连续递增子序列 nums [10,9,2,5,3,7,101,18] 求上面数组中的最长连续递增子序列&#xff0c;输出其长度 …

Python Requests 库:从安装到精通

摘要 本文详细介绍 Python Requests 库的安装与使用&#xff0c;通过常见示例让你轻松掌握。 一、引言 在当今的互联网时代&#xff0c;与各种 Web 服务进行交互是非常常见的需求。Python 作为一门功能强大且易于学习的编程语言&#xff0c;提供了许多用于网络请求的库&…