Spring @Transactional 注解完全指南

Spring @Transactional 注解完全指南

一、什么是 @Transactional

在开发Spring Boot项目时,我们常常会遇到这样的场景:一个方法里执行了多步数据库操作,中间某一步出错了,导致数据出现"半成功半失败"的情况------部分数据被提交,部分被回滚。这种数据不一致问题会带来严重的业务风险。

@Transactional 就是为了解决这个问题而生的。它的核心作用是:要么所有操作都成功提交,要么全部回滚作废,保证数据库的完整性和一致性。

@Transactional 是Spring框架提供的一个注解,用于声明事务边界。它允许开发者以声明式的方式管理事务,而无需手动编写开始、提交或回滚事务的代码。

二、实现原理

@Transactional 注解基于 AOP(面向切面编程) 实现。当Spring容器初始化时,会扫描标记了此注解的方法或类,并在这些方法执行前后自动织入事务管理逻辑。

具体来说,Spring会在运行时为带有 @Transactional 注解的类生成一个代理对象(JDK动态代理或CGLIB代理)。代理对象会在目标方法执行前后添加事务管理逻辑:

  • 方法执行前:开启一个新的事务
  • 方法执行后:如果方法执行成功,提交事务
  • 方法抛出异常:根据配置回滚事务

Spring事务管理的核心组件包括:事务管理器(PlatformTransactionManager)、事务定义(TransactionDefinition)、事务拦截器(TransactionInterceptor)等。

三、注解位置

@Transactional 可以应用于以下位置:

1. 方法级别(最常见)

java 复制代码
@Service
public class UserService {
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
    }
}

2. 类级别

java 复制代码
@Transactional(readOnly = true)
@Service
public class ReportService {
    // 所有方法默认为只读事务
    public List<Report> generateReport() { ... }
    
    @Transactional  // 覆盖类级别配置
    public void updateReportStatus(int id, String status) { ... }
}

类级别的注解会应用于该类的所有public方法,方法级别的注解会覆盖类级别的配置。

3. 接口或接口方法 (不推荐) Spring团队建议使用 @Transactional 注解来注解具体类的方法,而不是依赖接口中注解的方法,因为Java注解不能从接口继承。

四、参数详解

1. value / transactionManager

功能:指定事务管理器的名称。当存在多个事务管理器时,用于指定使用哪一个。

默认值:空字符串(使用默认事务管理器)。

示例

java 复制代码
@Transactional(value = "myTransactionManager")
public void myMethod() { ... }

使用场景:多数据源场景下,需要明确指定使用哪个数据源的事务管理器。

2. propagation------事务传播行为

功能:定义事务的传播行为,即当一个事务方法被另一个事务方法调用时,应该如何处理事务。

默认值Propagation.REQUIRED

传播行为 说明
REQUIRED(默认) 如果当前存在事务,则加入该事务;否则创建一个新事务
REQUIRES_NEW 总是创建一个新事务。如果当前存在事务,则挂起当前事务
SUPPORTS 如果当前存在事务,则加入;否则以非事务方式执行
NOT_SUPPORTED 以非事务方式执行。如果当前存在事务,则挂起该事务
MANDATORY 必须在一个现有事务中执行。如果没有事务,则抛出异常
NEVER 以非事务方式执行。如果当前存在事务,则抛出异常
NESTED 如果当前存在事务,则创建一个嵌套事务;否则等价于REQUIRED

使用场景

  • REQUIRED:绝大多数业务场景,多个操作需要在同一个事务中
  • REQUIRES_NEW:需要独立事务的场景,如操作日志记录------即使主业务失败,日志也要保存
  • NESTED:需要部分回滚的场景,内层事务失败不影响外层已执行的操作

3. isolation------事务隔离级别

功能:定义事务的隔离级别,控制并发事务之间的数据可见性。

默认值Isolation.DEFAULT(使用底层数据库的默认隔离级别)。

隔离级别 说明 脏读 不可重复读 幻读
READ_UNCOMMITTED 允许读取未提交的数据
READ_COMMITTED 只允许读取已提交的数据
REPEATABLE_READ 多次读取同一数据结果一致
SERIALIZABLE 最高的隔离级别,串行执行

使用场景

  • READ_COMMITTED:大多数应用的首选,平衡性能与一致性
  • SERIALIZABLE:对数据一致性要求极高的场景(如金融系统),但性能开销大

4. timeout

功能:设置事务的超时时间,单位为秒。超过此时间事务将被强制回滚。

默认值:-1(无超时限制)。

示例

java 复制代码
@Transactional(timeout = 30)  // 30秒超时
public void myMethod() { ... }

使用场景:防止事务长时间占用数据库资源,适用于预计执行时间较长的操作。

5. readOnly

功能:指定事务是否为只读事务。

默认值:false。

示例

java 复制代码
@Transactional(readOnly = true)
public List<User> findUsers() { ... }

使用场景

  • 纯查询操作设置为 true,可以帮助数据库引擎优化事务处理
  • 增删改操作必须为 false(默认值)

6. rollbackFor / rollbackForClassName

功能:指定哪些异常会导致事务回滚。

默认 :Spring默认只对未捕获的 RuntimeException 和 Error 触发回滚。

示例

java 复制代码
@Transactional(rollbackFor = {SQLException.class, IOException.class})
public void myMethod() throws SQLException, IOException { ... }

使用场景:当需要让检查异常(Checked Exception)也触发回滚时。

7. noRollbackFor / noRollbackForClassName

功能:指定哪些异常不会导致事务回滚。

示例

java 复制代码
@Transactional(noRollbackFor = {DataIntegrityViolationException.class})
public void myMethod() { ... }

使用场景:某些特定异常发生时希望提交已执行的操作,而不是全部回滚。

五、实际案例

案例一:用户注册(基础用法)

java 复制代码
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private AccountRepository accountRepository;
    
    @Transactional(rollbackFor = Exception.class)
    public void registerUser(User user, Account account) throws Exception {
        // 保存用户信息
        userRepository.save(user);
        // 创建账户
        accountRepository.save(account);
        // 如果这里抛出异常,两个操作都会回滚
    }
}

案例二:订单创建(事务传播)

java 复制代码
@Service
public class OrderService {
    @Autowired
    private InventoryService inventoryService;
    @Autowired
    private LogService logService;
    
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void createOrder(Order order) {
        // 扣减库存
        inventoryService.deductStock(order.getProductId(), order.getQuantity());
        // 保存订单
        orderRepository.save(order);
        // 记录操作日志(独立事务)
        logService.saveLog("创建订单:" + order.getId());
    }
}

@Service
public class LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(String message) {
        logRepository.save(new Log(message));
    }
}

即使订单创建失败回滚,日志记录也会独立提交,保证操作可追溯。

案例三:批量操作(嵌套事务)

java 复制代码
@Service
public class BatchService {
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void batchProcess(List<Data> dataList) {
        for (Data data : dataList) {
            processSingle(data);  // 每个单独处理,失败不影响其他
        }
    }
    
    @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void processSingle(Data data) {
        // 单条数据处理,失败只回滚当前条
        dataRepository.save(data);
    }
}

六、事务失效场景与踩坑指南

场景1:方法不是 public

Spring要求被代理的方法必须是 public 的,否则事务不会生效。

java 复制代码
@Service
public class UserService {
    @Transactional  // ❌ 无效!private方法不会生效
    private void updateUser(User user) {
        userRepository.save(user);
    }
    
    @Transactional  // ✅ 有效
    public void updateUserPublic(User user) {
        userRepository.save(user);
    }
}

在源码层面,AbstractFallbackTransactionAttributeSourcecomputeTransactionAttribute 方法中,如果目标方法不是 public,则 TransactionAttribute 返回 null,即不支持事务。

场景2:自身调用(同类方法调用)

这是最常见的踩坑点。当一个类内部的方法调用另一个带有 @Transactional 注解的方法时,事务不会生效。

java 复制代码
@Service
public class OrderService {
    public void placeOrder(Order order) {
        // ❌ 直接调用,不走代理,事务失效!
        this.deductInventory(order);
    }
    
    @Transactional
    public void deductInventory(Order order) {
        inventoryRepository.reduceStock(order.getProductId(), order.getQuantity());
    }
}

原因 :Spring事务基于代理实现,自调用使用的是 this(当前对象),而不是Spring生成的代理对象,事务拦截器不会被触发。

解决方案

方案一:拆分到不同类(推荐)

java 复制代码
@Service
public class OrderService {
    @Autowired
    private InventoryService inventoryService;
    
    public void placeOrder(Order order) {
        inventoryService.deductInventory(order);  // ✅ 通过代理调用
    }
}

@Service
public class InventoryService {
    @Transactional
    public void deductInventory(Order order) {
        inventoryRepository.reduceStock(order.getProductId(), order.getQuantity());
    }
}

方案二:使用 AopContext.currentProxy()

java 复制代码
@EnableAspectJAutoProxy(exposeProxy = true)  // 启动类上添加
@SpringBootApplication
public class Application { ... }

@Service
public class OrderService {
    public void placeOrder(Order order) {
        ((OrderService) AopContext.currentProxy()).deductInventory(order);
    }
    
    @Transactional
    public void deductInventory(Order order) { ... }
}

方案三:注入自身

java 复制代码
@Service
public class OrderService {
    @Autowired
    private OrderService self;
    
    public void placeOrder(Order order) {
        self.deductInventory(order);  // ✅ 通过代理调用
    }
    
    @Transactional
    public void deductInventory(Order order) { ... }
}

场景3:异常被 try-catch 捕获且未重新抛出

如果异常被 catch 住且没有重新抛出,Spring感知不到异常,会认为方法执行成功并提交事务。

java 复制代码
@Transactional
public void deleteDept(Long id) {
    try {
        deptMapper.delete(id);
        int x = 1 / 0;  // 触发异常
    } catch (Exception e) {
        System.out.println("发生异常,但事务未回滚");  // ❌ 异常被吃掉
    }
}

正确做法:让异常抛出去,或手动抛出新的 RuntimeException。

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void deleteDept(Long id) {
    try {
        deptMapper.delete(id);
        int x = 1 / 0;
    } catch (Exception e) {
        throw new RuntimeException("手动抛出异常,确保事务回滚", e);  // ✅
    }
}

场景4:异常类型不匹配

Spring默认只对 RuntimeExceptionError 回滚。如果抛出的是检查异常(Checked Exception),事务不会回滚。

java 复制代码
@Transactional  // ❌ IOException不会触发回滚
public void deleteDept(Long id) throws IOException {
    deptMapper.delete(id);
    throw new IOException("不会回滚!");
}

解决方案 :指定 rollbackFor

java 复制代码
@Transactional(rollbackFor = Exception.class)  // ✅ 所有异常都回滚
public void deleteDept(Long id) throws IOException {
    deptMapper.delete(id);
    throw new IOException("现在会回滚了!");
}

场景5:类未被Spring管理

如果类没有交给Spring容器管理(如缺少 @Service@Component 等注解),@Transactional 不会生效。

java 复制代码
// ❌ 没有@Service注解,不会被Spring管理
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void updateOrder(Order order) { ... }
}

场景6:数据库引擎不支持事务

以MySQL为例,MyISAM引擎不支持事务 ,需要使用 InnoDB引擎

sql 复制代码
-- 检查表引擎
SHOW TABLE STATUS LIKE 'your_table';
-- 修改为InnoDB
ALTER TABLE your_table ENGINE=InnoDB;

场景7:未配置事务管理器

在Spring Boot中,引入 spring-boot-starter-data-jdbcspring-boot-starter-data-jpa 后会自动配置事务管理器。但在传统Spring项目中需要手动配置:

java 复制代码
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

场景8:设置了不支持事务的传播行为

java 复制代码
@Transactional(propagation = Propagation.NOT_SUPPORTED)  // ❌ 不支持事务
public void updateOrder(Order order) { ... }

场景9:方法被 final 修饰

final 修饰的方法无法被代理,事务不会生效。

java 复制代码
@Transactional
public final void updateOrder(Order order) { ... }  // ❌ 事务失效

场景10:多线程调用

不同线程中的事务是独立的,@Transactional 无法跨线程传播事务。

七、最佳实践

  1. 始终指定 rollbackFor = Exception.class:确保所有异常都能触发回滚,避免检查异常导致事务不生效的坑。

  2. 事务范围最小化:尽量将事务控制在最小范围内,减少锁定资源的时间,提高系统并发能力。

  3. 避免自调用:将事务方法拆分到不同的Service类中,避免同类调用导致事务失效。

  4. 合理设置传播行为:理解不同传播行为的含义,选择最适合业务场景的模式。

  5. 只读事务加 readOnly = true:纯查询操作加上只读标志,有助于数据库优化。

  6. 谨慎使用 try-catch:在事务方法中捕获异常后务必重新抛出,否则事务不会回滚。

  7. 确保方法为 public:Spring事务代理只对public方法生效。

  8. 检查数据库引擎:使用MySQL时确保表引擎为InnoDB。

八、总结

@Transactional 是Spring框架中最强大也最容易被误用的注解之一。理解其背后的AOP代理机制是正确使用的关键。牢记以下几个核心要点:

  • 事务基于代理实现,只有通过代理对象调用才会生效
  • 默认只对 RuntimeException 回滚,建议显式指定 rollbackFor
  • 方法必须是 public
  • 自调用会绕过代理,导致事务失效
  • try-catch 捕获异常后必须重新抛出

掌握这些要点,就能在日常开发中优雅地使用 @Transactional,避免那些"事务明明加了却不生效"的困扰。

相关推荐
Java成神之路-1 小时前
G1 垃圾回收器 :SATB + TAMS核心机制深度解析
java·jvm
前端工作日常1 小时前
我学习到的JIT即时编译与机器码缓存失效
java·后端
带刺的坐椅2 小时前
Solon 的 10 种 HTTP 服务器:改一行依赖,换一个引擎
java·solon·jetty·undertow·mcp-server·htttp
xcLeigh2 小时前
Go入门:变量声明的五种方式详解
java·开发语言·golang
AI大模型-小华3 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
Mark_ZP3 小时前
【锁1】Synchronized vs ReentrantLock区别
java
立心者04 小时前
SpringBoot中使用TOTP实现MFA(多因素认证)
java·spring boot·后端
枕星而眠4 小时前
C++ STL Map容器完全指南:从有序红黑树到无序哈希表
java·开发语言
逃逸线LOF4 小时前
Spring配置数据源{连接池}(Druid、c3p0)
java·数据库·spring