Spring事务注解失效的15种场景及解决方案(超详细)

📌 前言

在Spring开发中,@Transactional 注解用起来很方便,但很多开发者都遇到过“明明加了事务注解,但事务就是不生效”的诡异问题。

本文总结了 15种 Spring事务失效的典型场景,每一种都有详细原因分析和解决方案,建议收藏!


🎯 事务失效的核心原因

Spring事务的本质: 基于AOP动态代理,通过代理对象调用方法才能增强事务。

一句话总结: 事务失效 = 代理没生效 = 方法没有被Spring代理对象调用


❌ 场景1:方法没有被public修饰

错误示例

java

@Service
public class UserService {
    @Transactional
    private void updateUser() {  // ❌ private方法
        // 事务不会生效
    }
}

原因

Spring事务代理默认只能对 public 方法进行增强,private/protected/默认 方法都不会生效。

✅ 解决方案

java

@Service
public class UserService {
    @Transactional
    public void updateUser() {  // ✅ 改为public
        // 事务生效
    }
}

❌ 场景2:同类中方法内部调用

错误示例

java

@Service
public class UserService {
    public void save() {
        update();  // ❌ 内部调用,绕过了代理
    }
    
    @Transactional
    public void update() {
        // 事务不会生效
    }
}

原因

内部调用是直接调用本类方法,没有经过Spring代理,事务无法增强。

✅ 解决方案(3种)

方案1: 将事务方法放到另一个Service

java

@Service
public class UserService {
    @Autowired
    private UpdateService updateService;
    
    public void save() {
        updateService.update();  // ✅ 通过代理调用
    }
}

方案2: 获取代理对象调用

java

@Service
public class UserService {
    @Autowired
    private ApplicationContext context;
    
    public void save() {
        UserService proxy = context.getBean(UserService.class);
        proxy.update();  // ✅ 通过代理调用
    }
    
    @Transactional
    public void update() {
        // 事务生效
    }
}

方案3: 自己注入自己(推荐)

java

@Service
public class UserService {
    @Autowired
    private UserService self;  // 注入代理对象
    
    public void save() {
        self.update();  // ✅ 通过代理调用
    }
    
    @Transactional
    public void update() {
        // 事务生效
    }
}

❌ 场景3:异常被try-catch吞掉了

错误示例

java

@Service
public class UserService {
    @Transactional
    public void update() {
        try {
            // 数据库操作
            int i = 1 / 0;  // 抛出异常
        } catch (Exception e) {
            // ❌ 异常被捕获,事务不会回滚
            System.out.println("异常被吞了");
        }
    }
}

原因

Spring事务回滚依赖于异常抛出,异常被捕获后事务管理器收不到信号。

✅ 解决方案

方案1: 手动回滚

java

@Transactional
public void update() {
    try {
        // 数据库操作
    } catch (Exception e) {
        // ✅ 手动回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

方案2: 重新抛出异常

java

@Transactional
public void update() throws Exception {
    try {
        // 数据库操作
    } catch (Exception e) {
        // ✅ 重新抛出
        throw e;
    }
}

方案3: 不捕获(最推荐)

java

@Transactional
public void update() {
    // 不捕获,让异常自动抛出
}

❌ 场景4:抛出的事务不回滚的异常类型

错误示例

java

@Transactional
public void update() throws Exception {
    // 数据库操作
    throw new Exception();  // ❌ Exception不会触发回滚
}

原因

Spring默认只对 RuntimeException 和 Error 进行回滚,Exception 不回滚。

✅ 解决方案

方案1: 指定回滚异常类型

java

@Transactional(rollbackFor = Exception.class)  // ✅ 指定
public void update() throws Exception {
    throw new Exception();  // 现在会回滚
}

方案2: 抛出RuntimeException

java

@Transactional
public void update() {
    throw new RuntimeException();  // ✅ 默认会回滚
}

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

错误示例

java

// MySQL 使用 MyISAM 引擎
// MyISAM 不支持事务!

原因

MySQL的MyISAM存储引擎不支持事务,需要InnoDB。

✅ 解决方案

sql

-- 查看表引擎
SHOW TABLE STATUS WHERE Name = 'user';

-- 修改为 InnoDB
ALTER TABLE user ENGINE = InnoDB;

❌ 场景6:多数据源配置错误

错误示例

java

@Configuration
public class DataSourceConfig {
    @Bean
    @Primary
    public DataSource dataSource1() { ... }
    
    @Bean
    public DataSource dataSource2() { ... }
    // ❌ 没有配置对应的 TransactionManager
}

✅ 解决方案

java

@Configuration
public class DataSourceConfig {
    @Bean
    @Primary
    public DataSource dataSource1() { ... }
    
    @Bean
    public PlatformTransactionManager transactionManager1() {
        return new DataSourceTransactionManager(dataSource1());
    }
    
    @Bean
    public DataSource dataSource2() { ... }
    
    @Bean
    public PlatformTransactionManager transactionManager2() {
        return new DataSourceTransactionManager(dataSource2());
    }
}

@Service
public class UserService {
    @Transactional(transactionManager = "transactionManager1")
    public void update1() { ... }
    
    @Transactional(transactionManager = "transactionManager2")
    public void update2() { ... }
}

❌ 场景7:事务传播特性使用不当

错误示例

java

@Service
public class OuterService {
    @Transactional
    public void outer() {
        innerService.inner();  // 调用内层方法
    }
}

@Service
public class InnerService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void inner() {
        // ❌ 外层异常会影响内层已提交的数据
    }
}

解决方案

理解7种传播特性的含义,根据业务选择正确的传播级别。

传播级别说明
REQUIRED默认,支持当前事务,没有就新建
REQUIRES_NEW新建事务,挂起当前事务
NESTED嵌套事务,回滚到保存点
SUPPORTS支持当前事务,没有就以非事务运行
NOT_SUPPORTED非事务运行,挂起当前事务
MANDATORY必须有当前事务,否则抛异常
NEVER必须非事务运行,否则抛异常

❌ 场景8:@Transactional 加在非Spring管理的类上

错误示例

java

// ❌ 普通类,没有被Spring管理
public class UserService {
    @Transactional  // 完全无效
    public void update() { }
}

原因

@Transactional 只有被Spring IoC容器管理的Bean才会生效。

✅ 解决方案

java

@Service  // ✅ 加上注解,交给Spring管理
public class UserService {
    @Transactional
    public void update() { }
}

❌ 场景9:方法中有多个数据库操作,但未开启事务

错误示例

java

@Service
public class OrderService {
    public void createOrder() {  // ❌ 没有事务注解
        orderDao.insert();    // 第1步成功
        inventoryDao.update(); // 第2步失败
        // 第1步不会回滚!
    }
}

✅ 解决方案

java

@Service
public class OrderService {
    @Transactional  // ✅ 加上事务注解
    public void createOrder() {
        orderDao.insert();
        inventoryDao.update();  // 失败时全部回滚
    }
}

❌ 场景10:事务超时设置不合理

错误示例

java

@Transactional(timeout = 1)  // 超时1秒
public void longRunningMethod() {
    // 执行超过1秒的操作
    // ❌ 事务会被自动回滚
}

✅ 解决方案

java

// 根据业务需求设置合适的超时时间
@Transactional(timeout = 30)  // 30秒
public void longRunningMethod() {
    // 业务操作
}

❌ 场景11:只读事务中执行写操作

错误示例

java

@Transactional(readOnly = true)
public void updateUser() {  // ❌ 只读事务中执行更新
    userDao.update();  // 可能抛出异常
}

✅ 解决方案

java

@Transactional(readOnly = false)  // 或直接不写
public void updateUser() {
    userDao.update();
}

❌ 场景12:Spring版本兼容性问题

错误示例

java

// Spring 3.x 对 @Transactional 的支持不完善
// 某些配置方式可能导致事务失效

✅ 解决方案

java

// 确保配置正确
@Configuration
@EnableTransactionManagement  // 开启事务支持
public class AppConfig {
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}

❌ 场景13:使用了错误的代理模式

错误示例

java

// 默认使用JDK动态代理(基于接口)
// 如果类没有实现接口,代理失败

✅ 解决方案

java

// 方案1:让类实现接口
public interface UserService { ... }
@Service
public class UserServiceImpl implements UserService { ... }

// 方案2:强制使用CGLIB代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Configuration
public class AppConfig { ... }

❌ 场景14:事务隔离级别设置不当

错误示例

java

@Transactional(isolation = Isolation.SERIALIZABLE)
public void update() {
    // SERIALIZABLE 级别性能极差,可能导致死锁
}

✅ 解决方案

java

// 根据业务需求选择合适的隔离级别
@Transactional(isolation = Isolation.READ_COMMITTED)  // 最常用
public void update() { ... }

❌ 场景15:多线程环境下事务失效

错误示例

java

@Transactional
public void batchUpdate() {
    // ❌ 每个子线程的事务独立
    taskExecutor.submit(() -> {
        userDao.update();  // 异常不会影响主事务
    });
}

原因

Spring事务和线程绑定(ThreadLocal),子线程的事务独立。

✅ 解决方案

java

// 方案1:不使用多线程
@Transactional
public void batchUpdate() {
    for (User user : userList) {
        userDao.update(user);  // 同步执行
    }
}

// 方案2:使用编程式事务
@Autowired
private TransactionTemplate transactionTemplate;

public void batchUpdate() {
    transactionTemplate.execute(status -> {
        // 多线程操作...
        return null;
    });
}

📊 问题速查表

症状可能原因
事务完全没生效方法不是public / 类没被Spring管理 / 内部调用
异常后不回滚异常被catch吞掉 / 抛出了非RuntimeException
部分回滚传播特性配置错误 / 多线程
数据库没变化引擎不支持事务 / 只读事务

✅ 最佳实践总结

  1. 统一规范@Transactional 加在 public 方法上

  2. 明确回滚:需要回滚时指定 rollbackFor = Exception.class

  3. 避免内部调用:通过代理对象调用事务方法

  4. 不要吞异常:要么不catch,要么手动回滚

  5. 数据库配置:确认使用 InnoDB 引擎

  6. 开启事务管理:配置 @EnableTransactionManagement


🎯 调试技巧

1. 开启事务日志

properties

# application.yml
logging.level.org.springframework.transaction=DEBUG
logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG

2. 查看代理是否生效

java

@Service
public class UserService {
    @Transactional
    public void update() { }
    
    @PostConstruct
    public void checkProxy() {
        System.out.println("是否是代理对象: " + AopUtils.isAopProxy(this));
        System.out.println("是否是CGLIB代理: " + AopUtils.isCglibProxy(this));
    }
}

📚 参考资料


💬 欢迎在评论区补充你遇到过的其他事务失效场景!
📢 关注我,获取更多Spring实战干货!

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值