【Spring】【事务】初学者直呼学会了的Spring事务入门

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD

🔥 2025本人正在沉淀中... 博客更新速度++

👍 欢迎点赞、收藏、关注,跟上我的更新节奏

📚欢迎订阅专栏,专栏名《# 在6月写Spring相关文章还算春天吗》

什么是Spring事务?

Spring事务是Spring框架提供的一种数据库事务管理机制,其核心目标是确保一组数据库操作要么全部成功提交,要么全部失败回滚,从而保证事务的ACID。

Spring事务基于底层数据库的事务能力(如MySQL的InnoDB引擎),并通过抽象和封装,使得开发者可以更方便地控制事务的边界和行为。

这里给忘记事务的UU们,回忆一波,数据库事务的ACID

事务的四大特性(ACID)

  • 原子性(Atomicity) :事务中的操作要么全部成功,要么全部失败回滚。
  • 一致性(Consistency) :事务执行前后,数据库的完整性约束(如外键、唯一性)必须保持一致。
  • 隔离性(Isolation) :多个事务并发执行时,彼此隔离,避免相互干扰。
  • 持久性(Durability) :事务提交后,修改永久保存到数据库中,即使系统崩溃也不丢失。

使用Spring事务

Spring的事务,分为编程式和声式事务。

简单来说就是,编程式事务是手动写,声式事务是用注释。

声明式事务

使用 @Transactional 注解 + Spring AOP 动态代理来实现,Spring 在启动时会扫描所有带有 @Transactional 的类或方法,并为其创建一个代理对象。当调用这些方法时,Spring 会在方法执行前后自动开启、提交或回滚事务。

typescript 复制代码
@Transactional
public void transferMoney() {
    // 数据库操作
}


// 等价于
proxy.transferMoney() {
    try {
        transactionManager.begin();
        target.transferMoney();
        transactionManager.commit();
    } catch (Exception e) {
        transactionManager.rollback();
        throw e;
    }
}

那让我们看看这个@Transactional注解。

我们可以看到它可以修饰在类(ElementType.TYPE)和方法(ElementType.METHOD)上。

less 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {...

当修饰在类上时:

  • 该类中所有 public 方法都会继承事务行为。
  • 所有方法都默认具有相同的事务属性(如传播行为、隔离级别等)。
  • 如果某个方法需要不同的事务配置,可以在该方法上单独添加 @Transactional并覆盖类级别的设置。
less 复制代码
@Service
@Transactional
public class OrderService {

    public void placeOrder() {
        // 数据库操作 1
        // 数据库操作 2
    }

    public void cancelOrder() {
        // 数据库操作
    }
}

修饰在方法上时:

  • 只对标注了 @Transactional 的方法进行事务增强。
  • 更细粒度地控制每个方法的事务行为(比如只读、超时、回滚规则等)。
  • 推荐用于大多数业务场景,更清晰可控。
typescript 复制代码
@Service
public class OrderService {

    @Transactional
    public void placeOrder() {
        // 操作数据库,事务管理生效
    }

    public void sendNotification() {
        // 不在事务中执行
    }
}

好奇的你一定会聪明,那@Transactional,可以设置啥呢?诶嘿嘿,那可多了:

字段名 作用描述 可选值/示例 默认值
propagation 事务传播行为:定义与现有事务的关系 REQUIRED, REQUIRES_NEW, SUPPORTS, NOT_SUPPORTED, MANDATORY, NEVER, NESTED REQUIRED(存在则加入,不存在则新建)
isolation 事务隔离级别:控制并发访问数据的可见性 READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE DEFAULT(通常为数据库默认级别)
timeout 超时时间(秒) :事务执行超过此时长自动回滚 正整数,例如 timeout = 30 -1(无超时限制)
readOnly 是否只读:提示数据库优化 (非强制约束) true(只读), false(可写) false(可写)
rollbackFor 触发回滚的异常类:指定哪些异常(含检查异常)应回滚事务 异常类数组,例如 rollbackFor = {SQLException.class} RuntimeExceptionError回滚
noRollbackFor 不触发回滚的异常类 :指定哪些 RuntimeException不触发回滚 异常类数组,例如 noRollbackFor = {IllegalArgumentException.class} 所有 RuntimeException均回滚
transactionManager 指定事务管理器:多数据源时选择特定事务管理器 Bean 事务管理器 Bean 名称,例如 transactionManager = "orderTxMgr" ""(使用默认事务管理器)

编程式事务

使用transactionTemplate进行事务管理,TransactionTemplate是Spring提供的一个工具类,用于以编程方式管理事务。它简化了事务代码的编写,避免了冗余的try-catch块。

typescript 复制代码
 @Autowired
 private TransactionTemplate transactionTemplate;

 public void performTransactionalOperation() {
     transactionTemplate.execute(status -> {
         // 执行数据库操作
         return null; // 返回值可以是任意结果对象
     });
 }

你是否会和我一样好奇,这个返回值是干嘛的?当然是获取事务的执行结果了,比如我们执行了一条update语句,我们可以返回更新成功的行数,如这样:

ini 复制代码
Integer rowsAffected = transactionTemplate.execute(status -> {
    return studentRepo.updateNameById("李四", 1L);
});

PlatformTransactionManager,这个更加的原始,通过手动调用其方法(如getTransactioncommit/rollback)直接控制事务

java 复制代码
 @Autowired
 private PlatformTransactionManager transactionManager;

 public void performManualTransaction() {
     TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
     try {
         // 执行数据库操作
         transactionManager.commit(status);
     } catch (Exception e) {
         transactionManager.rollback(status);
         throw e;
     }
 }

🙋主播,主播,那上面的@Transactional,可以设置一些事物相关的属性(传播行为、隔离级...) 这个手动挡,我们要怎么赋值勒?诶哟我,这个非常之简单:

如果你使用的是transactionTemplate,那你只要使用它的set方法就行了

csharp 复制代码
public void query() {
    transactionTemplate.setIsolationLevel(TransactionTemplate.ISOLATION_READ_COMMITTED);
    transactionTemplate.execute(status -> {
        Long cnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "z1"));
        return cnt;
    });
}

如果你使用的是PlatformTransactionManager那你可以对DefaultTransactionDefinition进行set赋值。

java 复制代码
public class SomeService {

    private final PlatformTransactionManager transactionManager;

    public SomeService(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void someMethod() {
        // 定义事务属性
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); // 设置隔离级别
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为
        def.setTimeout(30); // 设置超时时间(秒)
        def.setReadOnly(false); // 是否只读

        // 开启事务
        TransactionStatus status = transactionManager.getTransaction(def);

        try {
            // 执行业务逻辑
            // ...

            // 提交事务
            transactionManager.commit(status);
        } catch (Exception ex) {
            // 回滚事务
            transactionManager.rollback(status);
            throw ex;
        }
    }
}

我们可以康康DefaultTransactionDefinition可以设置什么:

你是否会好奇?这个setIsolationLevelsetIsolationLevelName有什么区别?

ini 复制代码
// 静态写法(推荐日常开发使用)
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);

// 动态写法(适合配置中心或参数传入)
String isolationStr = "ISOLATION_REPEATABLE_READ";
def.setIsolationLevelName(isolationStr)

事务传播行为(Propagation)

经过了上面的学习,聪明的你,一定学了Spring事务的基本使用,至少😄,可以在异常的时候,对数据进行回滚了,但是,好学的你一定会想要更多更多,下面我们来说说----Spring事务传播行为。

事务的传播行为(Transaction Propagation Behavior)是指:在多个方法相互调用时,事务应该如何传递和协调。简单来说,它决定了被调用方法是否要继续当前事务、开启新事务、还是不使用事务。

Spring中定义了7个传播行为

定义事务方法如何与现有事务交互:

  • REQUIRED(默认):如果当前存在事务,则加入,否则新建事务。
  • REQUIRES_NEW:无论当前是否存在事务,都新建事务。
  • NESTED:在当前事务中嵌套子事务,子事务可独立回滚。
  • SUPPORTS:如果当前存在事务,则加入;否则以非事务方式执行。
  • NOT_SUPPORTED:以非事务方式执行,挂起当前事务(如果存在)。
  • MANDATORY:强制要求当前存在事务,否则抛出异常。
  • NEVER:强制要求当前不存在事务,否则抛出异常。

REQUIRED(默认)

如果当前已经存在 一个事务,方法就加入这个现有事务,成为它的一部分。

如果当前不存在 事务,方法就自己开启一个新事务

下面的代码示例,主播想实验,methodB是一个事务,读不到methodA事务还没提交的数据。

java 复制代码
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    studentRepo.insert(new StudentPO(null, "a")); // 插入名字叫a的学生
    ((StudentService) AopContext.currentProxy()).methodB();
}

@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    Long aStuCnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "a"));
    log.info("aStuCnt:{}", aStuCnt); // 因为和methodA在同一个事务,这里会输出1
    studentRepo.insert(new StudentPO(null, "b"));
    throw new RuntimeException("error");
}

还有就是,如果methodB出现异常,就算在methodA异常被捕获,插入的学生a数据也会被回滚(因为methodA和methodB用的是一个同一个事务)

java 复制代码
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    studentRepo.insert(new StudentPO(null, "a"));
    try {
        ((StudentService) AopContext.currentProxy()).methodB();
    } catch (Exception e) {
        // 就算这里,捕获了异常,且不继续抛出,还是会导致之前插入的学生a数据回滚
    }
}

@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    Long aStuCnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "a"));
    log.info("aStuCnt:{}", aStuCnt);
    studentRepo.insert(new StudentPO(null, "b"));
    throw new RuntimeException("error");	
}

REQUIRES_NEW

REQUIRES_NEW 表示:无论当前是否存在事务,该方法都必须在独立的新事务中运行。

无外部事务时: 直接启动并运行于一个新事务中。

有外部事务时:

  1. 挂起当前事务: 立即挂起(suspended)已存在的外部事务。
  2. 创建新事务: 启动一个全新且独立的事务。
  3. 方法执行: 方法的所有操作在新事务上下文中执行。
  4. 新事务结束:
    • 成功(无异常): 提交新事务,更改永久生效。
    • 失败(有异常): 回滚新事务,撤销其所有更改。
  1. 恢复外部事务: 新事务结束后(无论提交或回滚),恢复之前挂起的外部事务,其后续操作不受新事务结果影响。
java 复制代码
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    studentRepo.insert(new StudentPO(null, "a"));	// 插入名字叫a的学生
    ((StudentService) AopContext.currentProxy()).methodB();
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    Long aStuCnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "a"));
    log.info("aStuCnt:{}", aStuCnt);	// 因为开启了新的事务,隔离级别是RR(默认),a事务插入的数据是查不到的,这里会是0
    studentRepo.insert(new StudentPO(null, "b"));
    throw new RuntimeException("error");
}

如果methodB出现异常,异常被捕获,插入的学生b数据会被回滚,但是学生a数据的插入不会被回滚(因为methodA和methodB用的是2个事务)

java 复制代码
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodA() {
    studentRepo.insert(new StudentPO(null, "a"));
    try {
        ((StudentService) AopContext.currentProxy()).methodB();
    } catch (Exception e) {
        // methodA和methodB是2个事务,methodB异常被捕获,没有继续抛出异常,插入的学生a数据不会被回滚
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    Long aStuCnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "a"));
    log.info("aStuCnt:{}", aStuCnt);	// 0
    studentRepo.insert(new StudentPO(null, "b")); // 插入学生b数据会被回滚
    throw new RuntimeException("error");
}

NESTED

NESTED 表示:在现有事务中嵌套一个可独立回滚的子事务(基于数据库 Savepoint)。

无外部事务时: 行为等同于 REQUIRED(开启新事务)。

有外部事务时:

  1. 创建保存点: 在当前事务内创建数据库保存点(Savepoint),标记子事务起点。
  2. 执行方法: 方法在嵌套子事务上下文中执行。⚠注意:这里的NESTED 传播行为中的"子事务"和"父事务"(外部事务)使用的是同一个物理数据库事务
  3. 子事务结束:
  • 成功(无异常): 子事务操作暂存,等待外部事务最终提交。
java 复制代码
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    studentRepo.insert(new StudentPO(null, "a")); // 插入名字叫a的学生
    ((StudentService) AopContext.currentProxy()).methodB();
}

@Transactional(propagation = Propagation.NESTED)
public void methodB() {
    Long aStuCnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "a"));
    log.info("aStuCnt:{}", aStuCnt); // 因为和methodA是同一个事务,所以这里是1
    studentRepo.insert(new StudentPO(null, "b"));
    throw new RuntimeException("error");
}
  • 失败(异常被捕获): 仅回滚子事务操作 (回滚到保存点),外部事务及其之前操作不受影响
java 复制代码
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    studentRepo.insert(new StudentPO(null, "a"));	// 插入学生a数据
    try {
        ((StudentService) AopContext.currentProxy()).methodB();
    } catch (Exception e) {
        // 异常被捕获但不抛出
        // 之前的插入学生a的数据不会被回滚
    }

}

@Transactional(propagation = Propagation.NESTED)
public void methodB() {
    Long aStuCnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "a"));
    log.info("aStuCnt:{}", aStuCnt);	// 1
    studentRepo.insert(new StudentPO(null, "b")); // 插入学生b数据
    throw new RuntimeException("error");	// 抛出异常,学生b数据回滚
}
  • 失败(异常未捕获或外部事务失败): 整个外部事务 (含所有嵌套操作)全部回滚
java 复制代码
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    studentRepo.insert(new StudentPO(null, "a")); // 插入学生a数据
    ((StudentService) AopContext.currentProxy()).methodB(); // 调用methodB异常,学生a数据回滚
}

@Transactional(propagation = Propagation.NESTED)
public void methodB() {
    Long aStuCnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "a"));
    log.info("aStuCnt:{}", aStuCnt); // 1
    studentRepo.insert(new StudentPO(null, "b")); // 插入学生b数据
    throw new RuntimeException("error"); // 抛出异常,学生b数据回滚
}
  1. 外部事务提交: 成功时,所有嵌套子事务的操作最终生效

SUPPORTS

SUPPORTS : 支持当前事务,若无事务则以非事务方式执行。

存在事务时: 方法加入 当前已存在的事务,成为其一部分。注意下面的代码例子,methodA的传播行为是Propagation.REQUIRED,它在没有事务的时候,会开启一个事务,让传播行为Propagation.SUPPORTSmethodB加入这个事务。

java 复制代码
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    studentRepo.insert(new StudentPO(null, "a")); // 插入学生a数据,此时事务还没有提交
    ((StudentService) AopContext.currentProxy()).methodB(); // 异常回滚学生a、b数据
}

@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
    Long aStuCnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "a"));
    log.info("aStuCnt:{}", aStuCnt);  // 1,因为是和methodA同一个事务,可以读到methodA没有提交的数据
    studentRepo.insert(new StudentPO(null, "b"));	// 插入学生b数据
    throw new RuntimeException("error");
}

不存在事务时: 方法在无事务上下文 中执行,操作直接提交(依赖数据库的 autocommit)。注意下面的代码例子,两个方法事务的传播行为都是Propagation.SUPPORTS

java 复制代码
@Transactional(propagation = Propagation.SUPPORTS) 
public void methodA() {
   studentRepo.insert(new StudentPO(null, "a")); // 事务1:插入学生a数据,插入成功后自动提交事务
   ((StudentService) AopContext.currentProxy()).methodB();
}

@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
    Long aStuCnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "a"));
    log.info("aStuCnt:{}", aStuCnt);	// 因为是新的事务,这里能读到事务1的数据,所以这里是1
    studentRepo.insert(new StudentPO(null, "b")); // 事务2:插入学生b数据,插入成功后自动提交事务
    throw new RuntimeException("error");
}

NOT_SUPPORTED

NOT_SUPPORTED : 不支持当前事务;始终以非事务方式执行,并暂停现有事务。

存在事务时:

  1. 挂起当前事务: Spring 会立即挂起(suspend) 任何已存在的事务。
  2. 非事务执行: 方法在无任何事务上下文中执行。
  3. 恢复事务: 方法执行完成后,恢复(resume) 之前挂起的事务。

不存在事务时:

  • 方法直接在无事务上下文中执行。

异常影响:

  • 无论调用时是否存在事务,方法都在非事务模式下运行。
  • 方法内部抛出的任何异常
    • 不会导致事务回滚(因为它不在事务中运行)。
    • 已执行的 SQL 操作无法回滚 (依赖数据库 autocommit,通常已立即生效)。
    • 只影响方法自身执行流程。
  • 如果外部事务被挂起,其状态不受该方法异常或成功的影响。恢复后,外部事务继续执行,仿佛该方法不存在于其事务上下文中。

一个代码直接结束😎,methodA在事务中执行,methodB非事务执行,所以下面的代码,插入的学生a数据会被回滚,

java 复制代码
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    studentRepo.insert(new StudentPO(null, "a")); // 插入学生数据a
    ((StudentService) AopContext.currentProxy()).methodB();
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB() {
    Long aStuCnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "a"));
    log.info("aStuCnt:{}", aStuCnt); // 0,因为methodA事务还没有提交(隔离级别默认RR)
    studentRepo.insert(new StudentPO(null, "b")); // 插入学生数据b
    throw new RuntimeException("error");	// 抛出异常,学生数据b不会被回滚
}

MANDATORY

MANDATORY 表示:必须存在一个事务,否则抛异常

它不会自己开启新事务,而是要求调用者必须已经开启了事务,即:只有在有事务上下文的前提下,该方法才能正常执行。所以下面方法,要是直接调用,会报错:

java 复制代码
@Transactional(propagation = MANDATORY)
public void query() {
    Long cnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "z1"));
}

报错内容

less 复制代码
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:362) ~[spring-tx-6.0.9.jar:6.0.9]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:601) ~[spring-tx-6.0.9.jar:6.0.9]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385) ~[spring-tx-6.0.9.jar:6.0.9]
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.0.9.jar:6.0.9]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.9.jar:6.0.9]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-6.0.9.jar:6.0.9]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:702) ~[spring-aop-6.0.9.jar:6.0.9]
	at top.flzjkl.service.StudentService$$SpringCGLIB$$0.query(<generated>) ~[classes/:na]

NEVER

NEVER : 绝对不支持当前事务;如果存在事务,则直接抛出异常。

存在事务时:

  • 立即抛出异常: Spring 会检测到当前存在活动事务,并立即抛出 IllegalTransactionStateException阻止方法执行

不存在事务时:

  • 方法在无事务上下文中正常执行。
  • 每条 SQL 通常立即生效(依赖数据库 autocommit)。

异常影响:

  • 有事务场景: 方法根本不会执行,由抛出的异常中断流程。
  • 无事务场景: 方法内部抛出的任何异常
    • 不会导致事务回滚(因为无事务)。
    • 已执行的 SQL 操作无法回滚
    • 只影响方法自身执行流程。
java 复制代码
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    studentRepo.insert(new StudentPO(null, "a"));
    ((StudentService) AopContext.currentProxy()).methodB(); // 这里直接就是异常,methodB的传播行为,不允许有事务
}

@Transactional(propagation = Propagation.NEVER)
public void methodB() {
    Long aStuCnt = studentRepo.selectCount(new LambdaQueryWrapper<StudentPO>().eq(StudentPO::getName, "a"));
    log.info("aStuCnt:{}", aStuCnt);
    studentRepo.insert(new StudentPO(null, "b"));
    throw new RuntimeException("error");
}

输出异常

less 复制代码
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.handleExistingTransaction(AbstractPlatformTransactionManager.java:413) ~[spring-tx-6.0.9.jar:6.0.9]
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:352) ~[spring-tx-6.0.9.jar:6.0.9]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:601) ~[spring-tx-6.0.9.jar:6.0.9]
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385) ~[spring-tx-6.0.9.jar:6.0.9]
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.0.9.jar:6.0.9]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.9.jar:6.0.9]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-6.0.9.jar:6.0.9]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:702) ~[spring-aop-6.0.9.jar:6.0.9]
	at top.flzjkl.service.StudentService$$SpringCGLIB$$0.methodB(<generated>) ~[classes/:na]
	at top.flzjkl.service.StudentService.methodA(StudentService.java:57) ~[classes/:na]

事务隔离级别(Isolation)

没啥好说的,和MySQL的隔离级别一个意思

控制事务之间的可见性,解决并发问题(脏读、不可重复读、幻读):

  • DEFAULT:使用数据库默认级别(如MySQL默认是REPEATABLE_READ)。
  • READ_UNCOMMITTED:允许读取未提交的数据(可能脏读)。
  • READ_COMMITTED:只能读取已提交的数据(解决脏读)。
  • REPEATABLE_READ:确保同一事务多次读取结果一致(解决不可重复读)。
  • SERIALIZABLE:完全串行化执行(解决幻读,性能最低)。

事务失效的场景

一般来说,声明式事务,比较容易出现事务失效,而声明式事务的原理是AOP。

好了,废话不多说,直接开始到事务失效的一些场景。

  1. 方法自调用(最常见也是最经典的陷阱):
  • 当一个类内部的方法A(非事务方法)调用同一个类内部的事务方法B时,由于调用发生在目标对象内部,绕过了Spring创建的代理对象,导致事务拦截器(AOP Advisor)无法介入,事务注解 @Transactional 失效。
  • 原因: Spring的事务管理是通过代理对象实现的。自调用发生在目标对象内部,没有经过代理。
  1. 事务方法定义非 public
  • Spring AOP 默认使用基于接口的JDK动态代理或基于类的CGLIB代理。对于非 public 方法:
    • JDK代理: 只能代理接口中的方法,非 public 方法不会被代理。
    • CGLIB代理: 虽然可以代理类,但无法代理 private 方法。protected 或包级私有的方法在CGLIB代理下理论上可以被代理,但Spring的事务基础架构( AbstractFallbackTransactionAttributeSource )默认只查找 ****public ****方法上的 ****@Transactional ****注解! 因此,非 public 方法上的 @Transactional 会被忽略。
  • 结论: 确保事务方法是 public 的。
  1. 异常类型不正确或被"吞掉":
    • 默认回滚异常: @Transactional 默认只在遇到运行时异常RuntimeException 及其子类)和错误Error)时才回滚。遇到检查型异常Exception 的子类,非 RuntimeException不会回滚
    • 自定义回滚异常: 可以使用 @Transactional(rollbackFor = MyCheckedException.class) 指定需要回滚的检查型异常。
    • 异常被捕获未抛出: 如果在事务方法内部捕获了异常(尤其是默认会触发回滚的 RuntimeExceptionError)并且没有重新抛出,那么事务管理器就感知不到异常的发生,事务会正常提交。这是导致事务"看似生效但未回滚"的常见原因。
    • 抛出非回滚异常: 如果抛出的异常类型不在默认回滚列表或 rollbackFor 指定的列表中,事务也不会回滚。
  1. 数据库引擎不支持事务:
  • 如果你使用的数据库存储引擎本身不支持事务(例如MySQL的MyISAM),那么无论Spring如何配置,事务都不可能生效。必须使用支持事务的引擎(如MySQL的InnoDB)。
  1. 未被Spring管理:
  • 包含 @Transactional 注解的Bean必须是由Spring容器创建和管理的。自己 new 出来的对象,其上的 @Transactional 注解无效。
  1. 传播行为设置不当:
  • 虽然传播行为本身是特性而非Bug,但如果理解错误可能导致事务行为不符合预期(例如,期望开启新事务但实际使用了 SUPPORTSMANDATORY 导致没有事务)。
  1. 多数据源下事务管理器配置错误:
  • 使用多个数据源时,必须为每个数据源配置对应的事务管理器(DataSourceTransactionManager)。在 @Transactional 注解或 TransactionTemplate 中需要明确指定使用哪个事务管理器(通过 valuetransactionManager 属性)。如果未正确指定,可能导致事务作用于错误的数据源或根本不生效。
相关推荐
恸流失24 分钟前
DJango项目
后端·python·django
Mr Aokey3 小时前
Spring MVC参数绑定终极手册:单&多参/对象/集合/JSON/文件上传精讲
java·后端·spring
地藏Kelvin4 小时前
Spring Ai 从Demo到搭建套壳项目(二)实现deepseek+MCP client让高德生成昆明游玩4天攻略
人工智能·spring boot·后端
菠萝014 小时前
共识算法Raft系列(1)——什么是Raft?
c++·后端·算法·区块链·共识算法
长勺4 小时前
Spring中@Primary注解的作用与使用
java·后端·spring
小奏技术5 小时前
基于 Spring AI 和 MCP:用自然语言查询 RocketMQ 消息
后端·aigc·mcp
编程轨迹5 小时前
面试官:如何在 Java 中读取和解析 JSON 文件
后端
lanfufu5 小时前
记一次诡异的线上异常赋值排查:代码没错,结果不对
java·jvm·后端
编程轨迹5 小时前
如何在 Java 中实现 PDF 与 TIFF 格式互转
后端
编程轨迹5 小时前
面试官:你知道如何在 Java 中创建对话框吗
后端