Spring AOP深度解析:代理机制、拦截器链与事务失效全解
一、JDK动态代理 vs CGLIB:代理机制的本质差异
Spring AOP通过代理模式实现横切逻辑织入,提供两种实现:JDK动态代理 (基于接口)和CGLIB(基于继承)。
1.1 JDK动态代理:接口为王
核心机制 :利用java.lang.reflect.Proxy在运行时创建接口的代理实现类。
java
// 目标接口
public interface UserService {
void saveUser(User user);
}
// 目标实现类
@Service
public class UserServiceImpl implements UserService {
@Override
public void saveUser(User user) {
// 业务逻辑
}
}
// JDK代理创建过程(Spring自动完成)
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
System.out.println("Before: " + method.getName());
// 调用目标方法
Object result = method.invoke(target, args);
// 后置增强
System.out.println("After: " + method.getName());
return result;
}
}
);
// 方法调用
proxy.saveUser(new User()); // 实际执行InvocationHandler.invoke()
内存结构:
┌──────────────────────────────────────┐
│ UserService代理对象 │
├──────────────────────────────────────┤
│ - InvocationHandler │
│ └─ target: UserServiceImpl │
│ - 实现UserService接口 │
│ └─ saveUser() → InvocationHandler.invoke()
└──────────────────────────────────────┘
关键特性:
- 只能代理实现了接口的类
- 代理对象与目标对象实现相同接口
- 通过反射调用目标方法(
method.invoke(target, args)) - 不能代理
final方法(接口方法天然非final)
1.2 CGLIB:继承的力量
核心机制 :通过字节码生成库继承目标类,重写方法实现代理。
java
// 目标类(无需接口)
@Service
public class OrderService {
public void createOrder(Order order) {
// 业务逻辑
}
}
// CGLIB代理创建(简化版)
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置增强
System.out.println("Before: " + method.getName());
// 通过MethodProxy调用(FastClass机制,比反射快3倍)
Object result = proxy.invokeSuper(obj, args);
// 后置增强
System.out.println("After: " + method.getName());
return result;
}
});
OrderService proxy = (OrderService) enhancer.create();
proxy.createOrder(new Order());
内存结构:
┌──────────────────────────────────────┐
│ OrderService$$EnhancerByCGLIB │
├──────────────────────────────────────┤
│ - 继承自OrderService │
│ - 重写createOrder()方法 │
│ └─ 调用MethodInterceptor.intercept()│
│ - 持有MethodInterceptor │
│ └─ 包含目标方法引用 │
└──────────────────────────────────────┘
关键特性:
- 可以代理无接口的类
- 代理对象继承目标类 (
OrderService$$EnhancerByCGLIB) - 使用FastClass机制(生成字节码直接调用,绕过反射)
- 不能代理
final类 、不能代理final方法
1.3 对比总结:Spring如何选择?
选择策略 (DefaultAopProxyFactory):
java
public class DefaultAopProxyFactory implements AopProxyFactory {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 1. 优化标志强制使用CGLIB
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config); // 接口仍用JDK
}
return new ObjenesisCglibAopProxy(config); // 类用CGLIB
}
// 2. 默认:有接口用JDK,无接口用CGLIB
return new JdkDynamicAopProxy(config);
}
}
配置强制使用CGLIB:
java
@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制CGLIB
public class Application { ... }
完整对比表:
| 特性 | JDK动态代理 | CGLIB | 性能影响 |
|---|---|---|---|
| 实现方式 | 实现接口 | 继承目标类 | CGLIB字节码生成耗时 |
| 代理条件 | 必须实现接口 | 类不能为final | - |
| 方法限制 | 接口方法 | 非final方法 | CGLIB final方法无法代理 |
| 调用机制 | 反射 (慢3倍) | FastClass (直接调用) | CGLIB高5-10% |
| 启动速度 | 快 | 慢(需生成字节码) | JDK代理快20ms/类 |
| 运行时性能 | 慢 (反射开销) | 快 (无反射) | CGLIB快5-10% |
| 内存占用 | 更少 | 稍多 (生成类) | JDK少约1KB/代理 |
| Spring Boot默认 | 有接口用JDK | 无接口用CGLIB | 自动选择最优方案 |
性能基准(JMH测试,10万次调用):
JDK动态代理: ~1200 ops/ms
CGLIB: ~1350 ops/ms (+12%)
直接调用: ~2000 ops/ms (基准)
结论 :CGLIB略快,但JDK代理启动更快、内存更少。Spring Boot的自动选择策略是最佳实践。
二、拦截器链:责任链模式的艺术
2.1 拦截器链执行模型
当调用代理方法时,Spring AOP构建MethodInterceptor链,按顺序执行。
java
// 拦截器链结构
public class ReflectiveMethodInvocation implements ProxyMethodInvocation {
private final Object proxy;
private final Object target;
private final Method method;
private final Object[] arguments;
private final List<MethodInterceptor> interceptors;
private int currentInterceptorIndex = -1;
// 核心:递归调用拦截器
@Override
public Object proceed() throws Throwable {
// 1. 所有拦截器执行完毕,调用目标方法
if (this.currentInterceptorIndex == this.interceptors.size() - 1) {
return invokeJoinpoint(); // 调用目标方法
}
// 2. 执行下一个拦截器
this.currentInterceptorIndex++;
MethodInterceptor interceptor = this.interceptors.get(currentInterceptorIndex);
return interceptor.invoke(this); // 传入this,用于递归proceed()
}
}
2.2 执行流程图解
场景:UserService.saveUser()有3个增强(前置日志、事务、后置日志)
调用 proxy.saveUser(user)
│
▼
┌────────────────────────────────────────────────────────────┐
│ ReflectiveMethodInvocation.proceed() │
│ interceptors = [LogInterceptor, TransactionInterceptor, │
│ LogAfterInterceptor] │
└────────────────────────────────────────────────────────────┘
│
├─▶ currentIndex = 0
├─▶ LogInterceptor.invoke(this)
│ │
│ ├─▶ System.out.println("Before: 日志记录")
│ │
│ └─▶ return methodInvocation.proceed()
│ │
│ ├─▶ currentIndex = 1
│ ├─▶ TransactionInterceptor.invoke(this)
│ │ │
│ │ ├─▶ TransactionStatus tx = txManager.getTransaction()
│ │ │
│ │ ├─▶ try {
│ │ │ Object result = methodInvocation.proceed()
│ │ │ │
│ │ │ ├─▶ currentIndex = 2
│ │ │ ├─▶ LogAfterInterceptor.invoke(this)
│ │ │ │ │
│ │ │ │ ├─▶ System.out.println("Before After")
│ │ │ │ │
│ │ │ │ └─▶ return methodInvocation.proceed()
│ │ │ │ │
│ │ │ │ ├─▶ currentIndex = 3
│ │ │ │ ├─▶ 所有拦截器执行完毕
│ │ │ │ └─▶ invokeJoinpoint()
│ │ │ │ │
│ │ │ │ └─▶ target.saveUser(user)
│ │ │ │ │
│ │ │ │ └─▶ 执行真实业务逻辑
│ │ │ │
│ │ │ └─▶ 返回结果
│ │ │
│ │ ├─▶ txManager.commit(tx)
│ │ └─▶ return result
│ │
│ └─▶ 返回结果
│
├─▶ System.out.println("After: 日志记录")
│
└─▶ 返回最终结果
关键特性 :拦截器链是递归结构 ,通过proceed()串联。每个拦截器可选择前置增强 、后置增强 或环绕增强。
2.3 TransactionInterceptor源码剖析
事务拦截器是最复杂、最重要的拦截器。
java
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 1. 获取目标类和方法
Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
// 2. 调用父类方法(事务模板方法)
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
}
// TransactionAspectSupport.invokeWithinTransaction()
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 1. 获取事务属性(@Transactional注解解析)
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
// 2. 获取事务管理器
final TransactionManager tm = determineTransactionManager(txAttr);
// 3. 转换为PlatformTransactionManager
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
// 4. 构建事务方法标识(类名.方法名)
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// 5. 创建事务信息
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 6. 执行目标方法(回调proceed)
retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {
// 7. 异常回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
// 8. 清理事务信息
cleanupTransactionInfo(txInfo);
}
// 9. 提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
事务状态管理:
java
// TransactionAspectSupport.createTransactionIfNecessary()
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr,
final String joinpointIdentification) {
// 如果没有事务属性,非事务执行
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// 获取事务状态(开启事务、传播行为处理)
TransactionStatus status = tm.getTransaction(txAttr);
return prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
}
}
// DataSourceTransactionManager.getTransaction()
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
Object transaction = doGetTransaction(); // 获取连接持有者
// 1. PROPAGATION_REQUIRED: 已存在事务则加入,否则新建
if (isExistingTransaction(transaction)) {
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// 2. 挂起当前事务(PROPAGATION_REQUIRES_NEW)
// 3. 创建新事务
return startTransaction(definition, transaction, debugEnabled, suspendedResources);
}
2.4 拦截器链顺序
默认顺序 (由@Order或Ordered接口控制):
java
// 1. 最高优先级:事务拦截器(@Order(Ordered.LOWEST_PRECEDENCE - 1))
TransactionInterceptor
// 2. 中间:自定义拦截器
@Aspect
@Order(1)
public class LoggingAspect { ... }
// 3. 最低优先级:缓存拦截器
CacheInterceptor
执行顺序 (前置增强 按升序 ,后置增强 按降序):
前置: Log(1) → Auth(2) → Transaction(3) → 目标方法
后置: Transaction(3) → Auth(2) → Log(1) → 返回
可视化:
LogAspect AuthAspect TransactionAspect Target
│ │ │ │
├─▶before() │ │ │
│ ├─▶before() │ │
│ │ ├─▶before() │
│ │ │ ├─▶执行方法
│ │ ├─▶after() │
│ ├─▶after() │ │
├─▶after() │ │ │
│ │ │ │
└─▶返回结果
三、@Transactional失效场景:高频踩坑
3.1 场景1:同类调用(Self-invocation)
这是最常见的失效场景(占70%的生产问题)。
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void createUser(User user) {
// 调用本类方法
saveUser(user); // ❌ @Transactional失效!
}
@Transactional // 事务注解在此处
public void saveUser(User user) {
userRepository.save(user);
if (user.getAge() < 0) {
throw new RuntimeException("年龄非法");
}
}
}
失效原因:
- Spring AOP基于代理对象 ,
createUser()调用的是this.saveUser()(this指向目标对象 ,而非代理对象) - 绕过代理 = 绕过拦截器链 = 事务拦截器未执行
内存模型:
UserService代理对象
│
├─▶ target: UserService目标对象
│
└─▶ createUser() → 不经过代理
│
└─▶ this.saveUser() // this是目标对象,无事务
│
└─▶ 直接执行方法,无AOP增强
UserService代理对象
│
└─▶ saveUser() → 经过代理
│
├─▶ TransactionInterceptor.invoke()
│ │
│ └─▶ 开启事务 → 调用目标 → 提交/回滚
│
└─▶ 目标.saveUser()
解决方案:
方案1:注入代理自身(推荐)
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// 注入自己的代理对象
@Autowired
private UserService selfProxy;
public void createUser(User user) {
selfProxy.saveUser(user); // ✅ 通过代理调用,事务生效
}
@Transactional
public void saveUser(User user) {
userRepository.save(user);
}
}
方案2:ApplicationContext获取代理
java
@Service
public class UserService {
@Autowired
private ApplicationContext applicationContext;
public void createUser(User user) {
UserService proxy = applicationContext.getBean(UserService.class);
proxy.saveUser(user); // ✅ 从Spring容器获取代理
}
@Transactional
public void saveUser(User user) { ... }
}
方案3:使用AopContext(不推荐)
java
// 需启用暴露代理配置
@EnableAspectJAutoProxy(exposeProxy = true)
public void createUser(User user) {
((UserService) AopContext.currentProxy()).saveUser(user);
}
方案4:重构代码(根本解决)
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserValidator userValidator;
@Transactional // 事务注解提升到入口方法
public void createUser(User user) {
userValidator.validate(user); // 校验逻辑分离
saveUser(user);
}
private void saveUser(User user) { // 私有方法,不暴露事务
userRepository.save(user);
}
}
3.2 场景2:异常被捕获不抛出
java
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
try {
orderRepository.save(order);
updateInventory(order); // 可能抛出异常
} catch (Exception e) {
log.error("创建订单失败", e);
// ❌ 异常被吞下,事务不会回滚
}
}
}
失效原因 :TransactionInterceptor只能拦截从目标方法抛出的异常。如果异常在方法内部被捕获,拦截器认为方法正常返回,将提交事务。
源码逻辑:
java
// TransactionInterceptor.invokeWithinTransaction()
try {
retVal = invocation.proceedWithInvocation(); // 执行目标方法
} catch (Throwable ex) {
completeTransactionAfterThrowing(txInfo, ex); // 异常处理
throw ex; // 重新抛出异常
}
commitTransactionAfterReturning(txInfo); // 正常返回则提交
解决方案:
方案1:捕获后重新抛出
java
@Transactional
public void createOrder(Order order) {
try {
orderRepository.save(order);
updateInventory(order);
} catch (InventoryException e) {
log.error("库存更新失败", e);
throw new RuntimeException("订单创建失败", e); // ✅ 重新抛出
}
}
方案2:手动回滚
java
@Transactional
public void createOrder(Order order) {
try {
orderRepository.save(order);
updateInventory(order);
} catch (InventoryException e) {
log.error("库存更新失败", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // ✅ 标记回滚
}
}
方案3:异常传播到上层
java
@Transactional
public void createOrder(Order order) throws InventoryException {
orderRepository.save(order);
updateInventory(order); // 不捕获,让异常传播
}
3.3 场景3:检查型异常不回滚
java
@Transactional
public void transferMoney(Account from, Account to, BigDecimal amount) throws InsufficientFundsException {
from.debit(amount); // 可能抛出检查型异常
to.credit(amount);
}
// ❌ InsufficientFundsException是检查型异常,默认不回滚
失效原因 :@Transactional默认只对运行时异常(RuntimeException)和错误(Error)回滚。
源码 (DefaultTransactionAttribute):
java
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error); // 默认策略
}
解决方案:
java
// 方案1:指定回滚异常
@Transactional(rollbackFor = {InsufficientFundsException.class,
RuntimeException.class})
public void transferMoney(Account from, Account to, BigDecimal amount)
throws InsufficientFundsException {
// ...
}
// 方案2:抛出自定义运行时异常
@Transactional
public void transferMoney(Account from, Account to, BigDecimal amount) {
try {
from.debit(amount);
} catch (InsufficientFundsException e) {
throw new BusinessRuntimeException("余额不足", e);
}
to.credit(amount);
}
3.4 场景4:非public方法
java
@Service
public class ReportService {
@Transactional
void generateReport() { // ❌ 包私有方法
// 事务失效
}
@Transactional
protected void sendReport() { // ❌ protected方法
// 事务失效
}
@Transactional
private void saveReport() { // ❌ 私有方法
// 事务失效
}
}
失效原因 :Spring AOP基于代理,默认使用JDK动态代理(接口)或CGLIB(继承),都只能代理public方法。非public方法无法被代理,拦截器无法织入。
源码验证:
java
// AbstractFallbackTransactionAttributeSource
protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
// 非public方法直接返回null(事务属性)
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// ...
}
解决方案:
java
// 方案:全部改为public
@Transactional
public void generateReport() { // ✅ public方法
// 事务生效
}
3.5 场景5:final方法(CGLIB)**
java
@Service
public class EmailService {
@Transactional
public final void sendEmail(Email email) { // ❌ final方法
// CGLIB无法重写,事务失效
}
}
失效原因 :CGLIB通过重写方法 实现代理。final方法不可重写,代理类无法覆盖,自然无法织入增强逻辑。
字节码层面观察:
java
// CGLIB生成的代理类
public class EmailService$$EnhancerByCGLIB extends EmailService {
// 重写所有非final public方法
@Override
public void someMethod() { ... }
// final方法未被重写,直接继承父类实现
// 因此调用时直接执行父类方法,无拦截器
}
解决方案:
java
// 移除final
@Transactional
public void sendEmail(Email email) { // ✅ 非final
// 事务生效
}
3.6 场景6:事务传播行为配置错误
java
@Service
public class PaymentService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deductBalance(Account account, BigDecimal amount) {
account.setBalance(account.getBalance().subtract(amount));
accountRepository.save(account);
}
}
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
try {
paymentService.deductBalance(order.getAccount(), order.getAmount());
} catch (InsufficientFundsException e) {
log.error("余额不足");
// 订单已保存,但deductBalance在独立事务
// 异常不会导致订单回滚 ❌ 数据不一致
}
}
}
问题分析:
REQUIRES_NEW创建新事务,与外层事务无关- 内部事务回滚不会导致外部事务回滚
- 可能导致数据不一致
正确做法:
java
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
// 不要使用REQUIRES_NEW,除非明确需要独立提交
paymentService.deductBalance(order.getAccount(), order.getAmount()); // 默认REQUIRED
}
3.7 场景7:多线程调用
java
@Service
public class AsyncService {
@Autowired
private UserRepository userRepository;
@Transactional
public void batchUpdate(List<User> users) {
users.parallelStream().forEach(user -> {
// 在新线程中执行
updateUser(user); // ❌ 新线程无事务上下文
});
}
@Transactional
public void updateUser(User user) {
userRepository.save(user);
}
}
失效原因 :事务上下文绑定到当前线程 (ThreadLocal)。新线程没有继承事务,导致每个updateUser()在独立事务(或无事务)中执行。
线程模型:
java
Thread-1 (主线程)
└─▶ TransactionStatus tx = TransactionAspectSupport.currentTransactionStatus();
└─▶ 存储在ThreadLocal中
Thread-2 (parallelStream线程)
└─▶ TransactionAspectSupport.currentTransactionStatus();
└─▶ ThreadLocal为空 → 无事务
解决方案:
方案1:避免在新线程中使用事务
java
@Transactional
public void batchUpdate(List<User> users) {
// 单线程处理
users.forEach(user -> {
updateUser(user); // 在同一线程
});
}
方案2:手动传递事务(不推荐)
java
// 复杂且易出错,应避免
方案3:使用编程式事务
java
@Autowired
private TransactionTemplate transactionTemplate;
public void batchUpdate(List<User> users) {
users.parallelStream().forEach(user -> {
transactionTemplate.execute(status -> {
userRepository.save(user);
return null;
});
});
}
四、最佳实践与规避策略
4.1 事务方法设计原则
原则1:入口方法必须是public
java
@Service
public class OrderApplicationService {
@Transactional
public void createOrder(CreateOrderCommand command) { // ✅ 入口public
orderDomainService.validate(command);
orderRepository.save(command.toOrder());
eventPublisher.publish(new OrderCreatedEvent(...));
}
}
// 领域服务方法可以是private(不暴露事务)
@Service
public class OrderDomainService {
private void validate(CreateOrderCommand command) { // ✅ 私有,被public方法调用
// 业务校验
}
}
原则2:不要在事务中远程调用
java
@Transactional
public void createOrder(Order order) {
orderRepository.save(order);
// ❌ 远程调用可能超时,导致事务长时间持有数据库连接
paymentClient.deduct(order); // 可能阻塞10秒
// ✅ 拆分到事务外
}
// 正确做法
public void createOrder(Order order) {
transactionTemplate.execute(status -> {
orderRepository.save(order);
});
paymentClient.deduct(order); // 事务外调用
}
原则3:事务粒度最小化
java
// ❌ 大事务
@Transactional
public void processAll() {
step1(); // 耗时1秒
step2(); // 耗时2秒
step3(); // 耗时3秒
}
// 持有锁6秒
// ✅ 小事务
public void processAll() {
transactionTemplate.execute(status -> step1());
transactionTemplate.execute(status -> step2());
transactionTemplate.execute(status -> step3());
}
// 各自持有锁1、2、3秒
4.2 自调用问题的终极解决方案
方案:应用服务层(Application Service)模式
java
// 接口层(无事务)
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderApplicationService appService;
@PostMapping
public ResponseEntity<Void> create(@RequestBody CreateOrderDTO dto) {
appService.createOrder(dto);
return ResponseEntity.ok().build();
}
}
// 应用服务层(事务边界)
@Service
public class OrderApplicationService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
@Transactional // ✅ 事务在应用服务层
public void createOrder(CreateOrderDTO dto) {
Order order = Order.from(dto);
orderRepository.save(order);
// 即使InventoryService是同Bean,通过@Autowired调用代理
inventoryService.deduct(order); // ✅ 通过代理调用,事务生效
}
}
// 领域服务层(无事务)
@Service
public class InventoryService {
@Autowired
private InventoryRepository inventoryRepository;
// 不需要事务,由调用方控制
public void deduct(Order order) {
Inventory inventory = inventoryRepository.findByProductId(order.getProductId());
inventory.deduct(order.getQuantity());
inventoryRepository.save(inventory);
}
}
架构优势:
- 明确事务边界在应用服务层
- 领域服务无事务依赖,可复用
- 通过
@Autowired调用,天然使用代理,避免自调用
4.3 异常处理策略
策略1:定义业务异常体系
java
// 业务异常基类(运行时)
public class BusinessException extends RuntimeException {
private final String errorCode;
private final String errorMessage;
public BusinessException(String errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
}
// 具体异常
public class InsufficientFundsException extends BusinessException {
public InsufficientFundsException() {
super("INSUFFICIENT_FUNDS", "余额不足");
}
}
策略2:统一异常转换
java
@Service
public class ExceptionTranslator {
public <T> T translate(Supplier<T> action) {
try {
return action.get();
} catch (SQLException e) {
throw new DataAccessException("数据库异常", e);
} catch (RemoteException e) {
throw new ServiceInvocationException("远程调用失败", e);
}
}
}
// 使用
@Transactional
public void createOrder(Order order) {
exceptionTranslator.translate(() -> {
orderRepository.save(order);
return null;
});
}
4.4 验证事务是否生效
测试代码:
java
@SpringBootTest
public class TransactionTest {
@Autowired
private UserService userService;
@Test
public void testTransactionRollback() {
assertThrows(RuntimeException.class, () -> {
userService.saveWithException(new User("test"));
});
// 验证数据未插入(事务回滚)
assertEquals(0, userRepository.count());
}
}
@Service
public class UserService {
@Transactional
public void saveWithException(User user) {
userRepository.save(user);
throw new RuntimeException("测试回滚");
}
}
五、总结:@Transactional失效自查清单
| 问题场景 | 原因 | 解决方案 | 验证方式 |
|---|---|---|---|
| 同类调用 | 绕过代理 | 注入自身/重构分层 | 日志打印代理类名 |
| 异常被吞 | 未抛出 | 重新抛出/setRollbackOnly | 断点调试 |
| 检查型异常 | 默认不回滚 | rollbackFor=Exception.class |
单元测试 |
| 非public方法 | 无法代理 | 改为public | 查看代理类 |
| final方法 | CGLIB无法重写 | 移除final | 反编译代理类 |
| 多线程 | ThreadLocal隔离 | 单线程/编程式事务 | 打印线程ID |
| REQUIRES_NEW | 独立事务 | 谨慎使用 | 检查传播行为 |
| 传播配置错误 | 事务分离 | 使用REQUIRED | 验证数据一致性 |
核心口诀:事务靠代理,代理要穿透,异常需传播,public是前提。