👋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 会在方法执行前后自动开启、提交或回滚事务。

@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)上。

@Target({ElementType.TYPE, ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Inherited

@Documented

public @interface Transactional {...

当修饰在类上时:

该类中所有 public 方法都会继承事务行为。

所有方法都默认具有相同的事务属性(如传播行为、隔离级别等)。

如果某个方法需要不同的事务配置,可以在该方法上单独添加 @Transactional并覆盖类级别的设置。

@Service

@Transactional

public class OrderService {

public void placeOrder() {

// 数据库操作 1

// 数据库操作 2

}

public void cancelOrder() {

// 数据库操作

}

}

修饰在方法上时:

只对标注了 @Transactional 的方法进行事务增强。

更细粒度地控制每个方法的事务行为(比如只读、超时、回滚规则等)。

推荐用于大多数业务场景,更清晰可控。

@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}

仅 RuntimeException和 Error回滚

noRollbackFor

不触发回滚的异常类:指定哪些 RuntimeException不触发回滚

异常类数组,例如 noRollbackFor = {IllegalArgumentException.class}

所有 RuntimeException均回滚

transactionManager

指定事务管理器:多数据源时选择特定事务管理器 Bean

事务管理器 Bean 名称,例如 transactionManager = "orderTxMgr"

""(使用默认事务管理器)

编程式事务

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

@Autowired

private TransactionTemplate transactionTemplate;

public void performTransactionalOperation() {

transactionTemplate.execute(status -> {

// 执行数据库操作

return null; // 返回值可以是任意结果对象

});

}

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

Integer rowsAffected = transactionTemplate.execute(status -> {

return studentRepo.updateNameById("李四", 1L);

});

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

@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方法就行了

public void query() {

transactionTemplate.setIsolationLevel(TransactionTemplate.ISOLATION_READ_COMMITTED);

transactionTemplate.execute(status -> {

Long cnt = studentRepo.selectCount(new LambdaQueryWrapper().eq(StudentPO::getName, "z1"));

return cnt;

});

}

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

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可以设置什么:

你是否会好奇?这个setIsolationLevel和setIsolationLevelName有什么区别?

// 静态写法(推荐日常开发使用)

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事务还没提交的数据。

@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().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用的是一个同一个事务)

@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().eq(StudentPO::getName, "a"));

log.info("aStuCnt:{}", aStuCnt);

studentRepo.insert(new StudentPO(null, "b"));

throw new RuntimeException("error");

}

REQUIRES_NEW

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

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

有外部事务时:

挂起当前事务: 立即挂起(suspended)已存在的外部事务。

创建新事务: 启动一个全新且独立的事务。

方法执行: 方法的所有操作在新事务上下文中执行。

新事务结束:

成功(无异常): 提交新事务,更改永久生效。

失败(有异常): 回滚新事务,撤销其所有更改。

恢复外部事务: 新事务结束后(无论提交或回滚),恢复之前挂起的外部事务,其后续操作不受新事务结果影响。

@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().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个事务)

@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().eq(StudentPO::getName, "a"));

log.info("aStuCnt:{}", aStuCnt); // 0

studentRepo.insert(new StudentPO(null, "b")); // 插入学生b数据会被回滚

throw new RuntimeException("error");

}

NESTED

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

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

有外部事务时:

创建保存点: 在当前事务内创建数据库保存点(Savepoint),标记子事务起点。

执行方法: 方法在嵌套子事务上下文中执行。⚠注意:这里的NESTED 传播行为中的“子事务”和“父事务”(外部事务)使用的是同一个物理数据库事务

子事务结束:

成功(无异常): 子事务操作暂存,等待外部事务最终提交。

@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().eq(StudentPO::getName, "a"));

log.info("aStuCnt:{}", aStuCnt); // 因为和methodA是同一个事务,所以这里是1

studentRepo.insert(new StudentPO(null, "b"));

throw new RuntimeException("error");

}

失败(异常被捕获): 仅回滚子事务操作(回滚到保存点),外部事务及其之前操作不受影响。

@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().eq(StudentPO::getName, "a"));

log.info("aStuCnt:{}", aStuCnt); // 1

studentRepo.insert(new StudentPO(null, "b")); // 插入学生b数据

throw new RuntimeException("error"); // 抛出异常,学生b数据回滚

}

失败(异常未捕获或外部事务失败): 整个外部事务(含所有嵌套操作)全部回滚。

@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().eq(StudentPO::getName, "a"));

log.info("aStuCnt:{}", aStuCnt); // 1

studentRepo.insert(new StudentPO(null, "b")); // 插入学生b数据

throw new RuntimeException("error"); // 抛出异常,学生b数据回滚

}

外部事务提交: 成功时,所有嵌套子事务的操作最终生效。

SUPPORTS

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

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

@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().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

@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().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 : 不支持当前事务;始终以非事务方式执行,并暂停现有事务。

存在事务时:

挂起当前事务: Spring 会立即挂起(suspend) 任何已存在的事务。

非事务执行: 方法在无任何事务上下文中执行。

恢复事务: 方法执行完成后,恢复(resume) 之前挂起的事务。

不存在事务时:

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

异常影响:

无论调用时是否存在事务,方法都在非事务模式下运行。

方法内部抛出的任何异常:

不会导致事务回滚(因为它不在事务中运行)。

已执行的 SQL 操作无法回滚(依赖数据库 autocommit,通常已立即生效)。

只影响方法自身执行流程。

如果外部事务被挂起,其状态不受该方法异常或成功的影响。恢复后,外部事务继续执行,仿佛该方法不存在于其事务上下文中。

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

@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().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 表示:必须存在一个事务,否则抛异常

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

@Transactional(propagation = MANDATORY)

public void query() {

Long cnt = studentRepo.selectCount(new LambdaQueryWrapper().eq(StudentPO::getName, "z1"));

}

报错内容

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() ~[classes/:na]

NEVER

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

存在事务时:

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

不存在事务时:

方法在无事务上下文中正常执行。

每条 SQL 通常立即生效(依赖数据库 autocommit)。

异常影响:

有事务场景: 方法根本不会执行,由抛出的异常中断流程。

无事务场景: 方法内部抛出的任何异常

不会导致事务回滚(因为无事务)。

已执行的 SQL 操作无法回滚。

只影响方法自身执行流程。

@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().eq(StudentPO::getName, "a"));

log.info("aStuCnt:{}", aStuCnt);

studentRepo.insert(new StudentPO(null, "b"));

throw new RuntimeException("error");

}

输出异常

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() ~[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。

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

方法自调用(最常见也是最经典的陷阱):

当一个类内部的方法A(非事务方法)调用同一个类内部的事务方法B时,由于调用发生在目标对象内部,绕过了Spring创建的代理对象,导致事务拦截器(AOP Advisor)无法介入,事务注解 @Transactional 失效。

原因: Spring的事务管理是通过代理对象实现的。自调用发生在目标对象内部,没有经过代理。

事务方法定义非 public :

Spring AOP 默认使用基于接口的JDK动态代理或基于类的CGLIB代理。对于非 public 方法:

JDK代理: 只能代理接口中的方法,非 public 方法不会被代理。

CGLIB代理: 虽然可以代理类,但无法代理 private 方法。protected 或包级私有的方法在CGLIB代理下理论上可以被代理,但Spring的事务基础架构( AbstractFallbackTransactionAttributeSource )默认只查找 ****public ****方法上的 ****@Transactional ****注解! 因此,非 public 方法上的 @Transactional 会被忽略。

结论: 确保事务方法是 public 的。

异常类型不正确或被“吞掉”:

默认回滚异常: @Transactional 默认只在遇到运行时异常(RuntimeException 及其子类)和错误(Error)时才回滚。遇到检查型异常(Exception 的子类,非 RuntimeException)不会回滚。

自定义回滚异常: 可以使用 @Transactional(rollbackFor = MyCheckedException.class)指定需要回滚的检查型异常。

异常被捕获未抛出: 如果在事务方法内部捕获了异常(尤其是默认会触发回滚的 RuntimeException 或 Error)并且没有重新抛出,那么事务管理器就感知不到异常的发生,事务会正常提交。这是导致事务“看似生效但未回滚”的常见原因。

抛出非回滚异常: 如果抛出的异常类型不在默认回滚列表或 rollbackFor 指定的列表中,事务也不会回滚。

数据库引擎不支持事务:

如果你使用的数据库存储引擎本身不支持事务(例如MySQL的MyISAM),那么无论Spring如何配置,事务都不可能生效。必须使用支持事务的引擎(如MySQL的InnoDB)。

未被Spring管理:

包含 @Transactional 注解的Bean必须是由Spring容器创建和管理的。自己 new 出来的对象,其上的 @Transactional 注解无效。

传播行为设置不当:

虽然传播行为本身是特性而非Bug,但如果理解错误可能导致事务行为不符合预期(例如,期望开启新事务但实际使用了 SUPPORTS 或 MANDATORY 导致没有事务)。

多数据源下事务管理器配置错误:

使用多个数据源时,必须为每个数据源配置对应的事务管理器(DataSourceTransactionManager)。在 @Transactional 注解或 TransactionTemplate 中需要明确指定使用哪个事务管理器(通过 value 或 transactionManager 属性)。如果未正确指定,可能导致事务作用于错误的数据源或根本不生效。