这是 Spring 框架的核心注解,用于实现事务管理(保证一组数据库操作要么全部成功,要么全部失败),先理解基础概念再拆解参数:
一、"事务" 的核心场景
比如转账操作:A 扣钱 + B 加钱,这两个数据库更新必须同时成功或同时失败。如果只执行了 A 扣钱就报错,事务会回滚,A 的扣钱操作也会撤销,避免数据不一致。
二、注解参数拆解
1、参数
- @Transactional 基础作用:给方法开启事务,Spring 会在方法执行前开启事务,执行成功后提交事务;
- rollbackFor = Exception.class 关键参数:
①Spring 事务默认只对 RuntimeException(运行时异常,如空指针、数组越界)和 Error 回滚 ,对 Checked Exception(编译时异常,如 IOException、SQLException)不回滚;
②rollbackFor = Exception.class 表示 "扩大回滚范围"------ 只要方法抛出 任何继承自 Exception 的异常(包括运行时异常和编译时异常),事务都会回滚;
③如果不写这个参数,遇到 SQLException(比如数据库连接失败)这类编译时异常,事务不会回滚,会导致数据错误。
java
@Service
public class OrderServiceImpl {
private static final Logger log = LoggerFactory.getLogger(SScenarioServiceImpl.class);
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
// 重写父类方法 + 事务控制(所有异常都回滚)
@Override
@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderDTO orderDTO) throws Exception {
// 操作1:保存订单主表
orderMapper.insert(orderDTO);
// 操作2:保存订单子表
orderItemMapper.batchInsert(orderDTO.getItems());
// 如果这里抛出任何Exception(比如空指针、数据库插入失败),上面两步操作都会回滚
if (orderDTO.getAmount() <= 0) {
throw new Exception("订单金额非法"); // 抛出Exception,事务回滚
}
}
}
如果保存订单子表时出错,订单主表的插入也会被撤销,保证数据一致性。
2、场景
这两个注解通常一起出现在:
- 实现类重写接口的业务方法;
- 这个方法涉及多步数据库操作(如增删改),需要事务保证数据一致性;
- 要求任何异常都触发事务回滚,避免部分操作成功导致数据脏污。
三、注意
- @Transactional 只对
public方法生效(Spring 代理机制决定),如果方法是private/protected,注解会失效; - 异常必须抛出到方法外部才会触发回滚:如果方法内部用
try-catch 捕获了异常但没重新抛出,事务不会回滚;
java
// 错误示例:捕获异常不抛出,事务不会回滚
@Override
@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderDTO orderDTO) {
try {
orderMapper.insert(orderDTO);
throw new Exception("出错了");
} catch (Exception e) {
// 只打印日志,没抛出异常,事务会提交(数据错误)
log.error("创建订单失败", e);
}
}
- rollbackFor 的值是异常类型,可以指定多个,比如 rollbackFor = {SQLException.class,IOException.class}。
四、log
为什么不用 System.out.println()?
这行代码的核心价值是替代原生的 System.out.println(),因为日志框架有这些优势:
- 可以灵活控制日志级别(只输出错误 / 警告,不输出调试信息);
- 日志可输出到文件、控制台、远程服务器等,且格式可配置;
- 性能更好,支持异步日志。
可以用 Lombok 的 @Slf4j 或 Spring 的 @Log 注解自动生成 log 对象,省去手动定义的代码;
1、方法一
如果不定义直接写 log.error(...),编译器会报错:找不到符号 log,因为它不知道 log 是什么。
java
public class SScenarioServiceImpl {
// 第一步:定义日志对象(必须有)
private static final Logger log = LoggerFactory.getLogger(SScenarioServiceImpl.class);
@Override
@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderDTO orderDTO) {
try {
// 业务逻辑
} catch (Exception e) {
// 第二步:使用日志对象输出错误日志
log.error("创建订单失败", e);
throw e; // 抛出异常触发事务回滚
}
}
}
2、方法二(@Slf4j 注解)
使用 Lombok 的 @Slf4j 注解
这是日常开发中最常用的简化方式,添加 Lombok 依赖后,只需要加一个注解,编译器会自动帮你生成和上面一样的 log 对象:
java
import lombok.extern.slf4j.Slf4j;
@Slf4j // 自动生成 private static final Logger log = ...
public class SScenarioServiceImpl {
public void createOrder(OrderDTO orderDTO) {
try {
// 业务逻辑
} catch (Exception e) {
// 直接用 log,无需手动定义
log.error("创建订单失败", e);
throw e;
}
}
}
3、方法三(@Log 注解)
Spring Boot 3.x+ 的 @Log 注解
Spring 自身也提供了类似的简化注解,效果和 Lombok 一致:
java
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.Log;
@Log // 自动生成 log 对象
@Component
public class SScenarioServiceImpl {
// 直接使用 log.error(...)
}
4、总结
- 不管是手动定义还是注解简化,最终都必须有一个 Logger 类型的 log 对象,才能调用 log.error()、log.info() 等方法;
- log 对象命名是行业约定:几乎所有项目都用 log 作为变量名,不要写成 logger、logUtil 等,保持代码规范;
- LoggerFactory.getLogger(SScenarioServiceImpl.class) 中的 Class参数不能错:它决定了日志输出时的 "类名标识",方便定位日志来源(比如日志里会显示 SScenarioServiceImpl这个类抛出的错误)。
总结
@Transactional(rollbackFor = Exception.class) 是 Spring 注解,给方法加事务控制,核心是所有 Exception 异常都会触发事务回滚,保证数据库操作的原子性;