当前位置: 首页 > article >正文

Java实用注解篇:@Transactional 事务失效的场景深度解析

前言

       在使用 @Transactional 时,很多开发者都会遇到一个常见困惑:明明加了事务注解,但事务却没有生效,数据库操作仍然被提交了!
        这是因为事务机制的触发有一些 前提条件,只要触碰到事务失效的“雷区”,就会让事务变成“摆设”。接下来,我们来详细剖析几种常见的 事务失效场景 以及 解决方案

1️⃣ 同一个类中方法直接调用,事务失效

问题复现:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void methodA() {
        userRepository.save(new User("UserA"));
        methodB(); // 内部调用,不触发事务
    }

    @Transactional
    public void methodB() {
        userRepository.save(new User("UserB"));
        throw new RuntimeException("Test rollback"); // 期望回滚
    }
}

执行结果:

  • UserA 会被插入数据库
  • UserB 没有插入,因为抛出了异常
  • 🚨 事务没有回滚!

为什么会失效?

       Spring 的事务是基于 AOP 代理机制 实现的,而代理对象只有在 外部调用 时才会触发拦截器逻辑,从而开启事务。
        同一个类内的方法调用不会经过代理对象,而是通过 this.methodB() 调用的,因此不会触发事务机制!

解决方案:

方案一:通过代理对象调用事务方法

@Autowired
private UserService userService;

@Transactional
public void methodA() {
    userRepository.save(new User("UserA"));
    userService.methodB(); // 通过代理对象调用,触发事务
}

方案二:使用 AopContext 获取当前代理对象

Spring 提供了 AopContext 工具类,可以获取当前代理对象:

import org.springframework.aop.framework.AopContext;

@Transactional
public void methodA() {
    userRepository.save(new User("UserA"));
    ((UserService) AopContext.currentProxy()).methodB();
}

注意:

  • 需要在配置类中开启 暴露代理对象功能 才能使用 AopContext
@EnableAspectJAutoProxy(exposeProxy = true)

2️⃣ 非 public 方法上的 @Transactional 注解无效

问题复现:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    private void addUser() {
        userRepository.save(new User("PrivateUser"));
        throw new RuntimeException("Test rollback");
    }
}

执行结果:

  • 数据成功插入,事务 没有回滚

为什么会失效?

Spring 的事务是基于 动态代理机制,只有 public 方法 才会被代理,privateprotecteddefault 修饰的方法不会被代理,因此不会触发事务机制。

解决方案:

改为 public 方法:

@Transactional
public void addUser() {
    userRepository.save(new User("PublicUser"));
    throw new RuntimeException("Test rollback");
}

3️⃣ 数据库引擎不支持事务

问题复现:

如果你的数据库表使用的是 MySQL 的 MyISAM 引擎 而不是 InnoDB,事务机制是不会生效的!

检查表引擎:

SHOW TABLE STATUS WHERE Name = 'your_table';

如果看到 Engine=MyISAM

+------------+--------+ ...
| Name       | Engine | ...
+------------+--------+ ...
| user_table | MyISAM | ...
+------------+--------+ ...

为什么会失效?

  • MyISAM 是 MySQL 的早期存储引擎,不支持事务回滚、外键等特性。
  • InnoDB 才是支持事务、行级锁等功能的存储引擎。

解决方案:

把表引擎改为 InnoDB:

ALTER TABLE your_table ENGINE=InnoDB;

查看所有 MyISAM 表:

SELECT table_schema, table_name, engine
FROM information_schema.tables
WHERE engine = 'MyISAM';

4️⃣ 捕获了异常,导致事务无法回滚

问题复现:

@Transactional
public void addUser() {
    try {
        userRepository.save(new User("TryCatchUser"));
        int result = 1 / 0; // 抛出 ArithmeticException
    } catch (Exception e) {
        // 异常被捕获
        System.out.println("Exception caught: " + e.getMessage());
    }
}

执行结果:

  • 数据被插入
  • 🚨 事务没有回滚!

为什么会失效?

  • Spring 默认只有 未捕获的运行时异常(继承自 RuntimeException)才会触发回滚。
  • 受检异常(如 IOException)或者 被捕获的异常 不会触发事务回滚!

解决方案:

方案一:手动抛出异常,让 Spring 感知到事务需要回滚:

@Transactional
public void addUser() {
    try {
        userRepository.save(new User("User"));
        int result = 1 / 0;
    } catch (Exception e) {
        throw new RuntimeException(e); // 抛出运行时异常触发回滚
    }
}

方案二:使用 rollbackFor 明确指定哪些异常触发回滚:

@Transactional(rollbackFor = Exception.class)
public void addUser() {
    try {
        userRepository.save(new User("User"));
        int result = 1 / 0;
    } catch (Exception e) {
        // 异常依然被捕获,但事务仍然会回滚
        System.out.println("Caught exception, but rollback still happens!");
    }
}

5️⃣ 多线程环境下事务失效

问题复现:

@Transactional
public void addUser() {
    new Thread(() -> {
        userRepository.save(new User("AsyncUser"));
        throw new RuntimeException("Test rollback");
    }).start();
}

执行结果:

  • 数据被插入
  • 🚨 事务没有回滚!

为什么会失效?

Spring 的事务是 线程绑定的(基于 ThreadLocal 实现),
新线程不会继承原线程的事务上下文,因此新线程无法感知到原线程的事务边界。

解决方案:

使用 @Async 配合事务:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Async
    @Transactional
    public void addUserAsync() {
        userRepository.save(new User("AsyncUser"));
        throw new RuntimeException("Test rollback");
    }
}

注意:

  • 需要在启动类上加上 @EnableAsync
@SpringBootApplication
@EnableAsync
public class Application {
}

6️⃣ 数据库连接的自动提交未关闭

问题复现:

如果你的数据库连接池配置了 自动提交,那么事务控制会被绕过!

可能的配置:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test_db
    username: root
    password: 123456
    hikari:
      auto-commit: true  # 🚨自动提交为true

为什么会失效?

  • Spring 事务会通过 DataSource 获取连接,如果连接的 autoCommit=true,每次 SQL 执行都会立刻提交。
  • 事务注解控制的提交/回滚行为被绕过

解决方案:

  • 确保数据库连接池关闭自动提交
spring:
  datasource:
    hikari:
      auto-commit: false
  • 或者手动关闭自动提交:
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);

7️⃣ 代理对象被绕过(比如使用 this 关键字)

问题复现:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void outerMethod() {
        userRepository.save(new User("OuterMethod"));
        this.innerMethod(); // 🚨不会触发事务
    }

    @Transactional
    public void innerMethod() {
        userRepository.save(new User("InnerMethod"));
        throw new RuntimeException("Test rollback");
    }
}

为什么会失效?

  • Spring 的事务是基于代理对象实现的this.innerMethod() 是直接调用自身方法,绕过了代理对象。
  • 事务拦截器不会生效,因此内部方法不会走事务逻辑。

解决方案:

  • 通过代理对象调用事务方法:
@Autowired
private UserService userService;

public void outerMethod() {
    userRepository.save(new User("OuterMethod"));
    userService.innerMethod(); // 🚀触发事务
}

8️⃣ 嵌套事务设置不当

问题复现:

当你有嵌套事务时,如果 传播机制(Propagation) 配置不当,也可能造成事务失效。

例如:

@Transactional
public void outerMethod() {
    userRepository.save(new User("Outer"));
    innerMethod();
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerMethod() {
    userRepository.save(new User("Inner"));
    throw new RuntimeException("Test rollback");
}

为什么会失效?

@Transactional(propagation = Propagation.NESTED)
  • Propagation.REQUIRES_NEW 表示开启一个新的事务,
    外层事务和内层事务是独立的,即使内层回滚,外层不会受影响。
  • 所以 Outer 数据仍然会被提交!

解决方案:

  • 如果你期望事务统一回滚,改成默认的 Propagation.REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void innerMethod() {
    userRepository.save(new User("Inner"));
    throw new RuntimeException("Test rollback");
}
  • 如果你想实现嵌套事务,可以考虑 NESTED 传播机制

9️⃣ 事务管理器配置错误

问题复现:

当你使用了多个数据源时,如果事务管理器没有正确配置,也会造成事务失效:

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

为什么会失效?

  • 如果你的数据源和事务管理器不匹配,比如有多个数据源,但事务管理器只绑定了一个数据源,
    其他数据源的事务不会生效
  • 没有声明事务管理器 时,Spring 会使用 默认事务管理器,可能不是你想要的那个。

解决方案:

  • 指定事务管理器:
@Transactional(transactionManager = "transactionManager")
  • 多数据源事务管理器配置:
@Bean("transactionManager1")
public PlatformTransactionManager transactionManager1(@Qualifier("dataSource1") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

@Bean("transactionManager2")
public PlatformTransactionManager transactionManager2(@Qualifier("dataSource2") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

🔟 被代理类不是 Spring 管理的 Bean

问题复现:

public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Transactional
    public void addUser() {
        userRepository.save(new User("User"));
        throw new RuntimeException("Test rollback");
    }
}

调用:

UserService userService = new UserService();
userService.addUser(); // 🚨事务失效

为什么会失效?

  • Spring 事务依赖于 AOP 代理,只有被 Spring 托管的 Bean 才会启用代理机制。
  • 手动 new 对象不会触发事务机制

解决方案:

  • 让 Spring 托管 Bean:
@Service
public class UserService {
    ...
}
  • 使用 ApplicationContext 获取代理对象:
@Autowired
private ApplicationContext applicationContext;

public void addUser() {
    UserService proxy = applicationContext.getBean(UserService.class);
    proxy.addUser(); // 🚀事务生效
}

1️⃣1️⃣ SpringBoot 的 @EnableTransactionManagement 未生效

问题复现:

如果你在项目中使用的是 SpringBoot,但是 @Transactional 却完全没反应,
可能是因为事务管理器没有启用!

为什么会失效?

  • SpringBoot 默认开启事务管理(自动配置),但如果你自己创建了配置类,并关闭了自动配置,就可能导致事务失效。

解决方案:

  • 显式开启事务管理器:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
}
  • 确认事务管理器是否生效:
@Autowired
private PlatformTransactionManager transactionManager;

@PostConstruct
public void checkTransactionManager() {
    System.out.println("Transaction Manager: " + transactionManager);
}

1️⃣2️⃣ 数据库本身的问题

最后,有时候 不是代码问题,而是 数据库层面的配置问题 导致事务失效:

  • 数据库权限不足
    如果数据库用户权限不足,无法执行 ROLLBACK 操作,也会让事务无法回滚。

  • 触发器 (Trigger)
    如果表有触发器,会导致某些数据在触发器执行后直接提交,绕过事务管理。

🎯 总结:事务失效场景大全

事务失效场景失效原因解决方案
同类方法调用没有代理通过代理对象调用
非 public 方法只有 public 方法才会被代理改为 public
数据库引擎不支持事务使用 MyISAM 而不是 InnoDB修改为 InnoDB
异常被捕获Spring 默认只回滚 RuntimeException显式指定 rollbackFor
多线程调用新线程不会继承原线程的事务使用 @Async 结合事务
自动提交未关闭数据源 autoCommit=true关闭自动提交
代理对象被绕过this 调用自身方法使用代理对象调用
嵌套事务传播机制设置不当REQUIRES_NEW 导致事务隔离使用 NESTED 或 REQUIRED
事务管理器配置错误数据源和事务管理器不匹配确保事务管理器正确绑定数据源
Bean 非 Spring 托管new 对象不会触发代理机制让 Spring 托管 Bean
@EnableTransactionManagement 未开启没有启用事务管理器显式开启事务管理器
数据库层面问题MySQL 触发器或权限问题检查数据库配置

思考题:

  1. 如何在同一类方法调用时,确保事务能够生效?
  2. 为什么捕获异常后事务不会回滚?
  3. 事务和线程之间的关系是怎样的?

如果觉得这篇文章对你有帮助,记得点赞⭐、收藏📌、关注🚀!


http://www.kler.cn/a/581914.html

相关文章:

  • BambuStudio学习笔记:MultiMaterialSegmentation
  • 在Spring Boot项目中如何实现获取FTP远端目录结构
  • 架构师之路——设计模式篇(总览)
  • 【鸿蒙开发】Hi3861学习笔记- GPIO之LED
  • 数据安全之策:备份文件的重要性与自动化实践
  • C#与Python的差别
  • 如何在Spring Boot中校验用户上传的图片文件的两种方法
  • C语言(23)
  • 系统架构设计师-第5章 计算机网络
  • nextjs15简要介绍以及配置eslint和prettier
  • 【MySQL是怎么运行的】0、名词解释
  • 分布式 ID 设计方案
  • Rust 之一 基本环境搭建、各组件工具的文档、源码、配置
  • 【每日八股】Redis篇(四):持久化(下)
  • 机器学习中的梯度下降是什么意思?
  • 图像识别技术与应用课后总结(16)
  • Spring MVC 详细分层和微服务
  • Redis线上问题排查指南:常见错误与解决思路
  • 统计登录系统10秒内连续登录失败超过3次的用户
  • 计算机三级网络技术备考(5)