Spring事务失效的常见原因

发布于:2025-08-13 ⋅ 阅读:(12) ⋅ 点赞:(0)

Spring 事务失效的常见原因

Spring 事务管理(@Transactional)是开发中常用的功能,但在某些情况下事务可能失效。以下是常见的事务失效原因、示例及对应的解决方案。


1. 方法访问权限问题(非 public 方法)

原因:Spring为方法创建代理、添加事务通知前提条件都是该方法是public的,Spring AOP 代理机制要求 @Transactional 只能应用于 public 方法,否则事务不会生效。

示例

@Service
public class UserService {
    @Transactional
    private void createUser(User user) {  // 事务失效
        userDao.save(user);
    }
}

解决方案
确保 @Transactional 方法为 public

@Service
public class UserService {
    @Transactional
    public void createUser(User user) {  // 事务生效
        userDao.save(user);
    }
}

2. 自调用问题(同一个类内部调用)

原因:Spring 事务基于 AOP 代理,自调用时不会经过代理类,导致事务失效。

示例

@Service
public class OrderService {
    public void placeOrder(Order order) {
        // 其他逻辑
        this.saveOrder(order);  // 自调用,事务失效
    }

    @Transactional
    public void saveOrder(Order order) {
        orderDao.save(order);
    }
}

解决方案
方法1:拆分到不同类

@Service
public class OrderService {
    @Autowired
    private OrderTransactionService orderTransactionService;

    public void placeOrder(Order order) {
        orderTransactionService.saveOrder(order);  // 代理生效
    }
}

@Service
public class OrderTransactionService {
    @Transactional
    public void saveOrder(Order order) {
        orderDao.save(order);
    }
}

方法2:使用 AopContext.currentProxy()(需开启 @EnableAspectJAutoProxy(exposeProxy = true)

@Service
public class OrderService {
    public void placeOrder(Order order) {
        ((OrderService) AopContext.currentProxy()).saveOrder(order);  // 代理调用
    }

    @Transactional
    public void saveOrder(Order order) {
        orderDao.save(order);
    }
}

3. 异常被捕获未抛出

原因:默认情况下,Spring 事务只在抛出 RuntimeExceptionError 时回滚。如果异常被 catch 但没有重新抛出,事务不会回滚。

示例

@Service
public class AccountService {
    @Transactional
    public void transfer(Account from, Account to, BigDecimal amount) {
        try {
            accountDao.deduct(from, amount);
            accountDao.add(to, amount);
        } catch (Exception e) {
            log.error("转账失败", e);  // 异常被捕获,事务不回滚
        }
    }
}

解决方案
方法1:重新抛出异常

catch (Exception e) {
    log.error("转账失败", e);
    throw new RuntimeException(e);  // 触发回滚
}

方法2:指定 @Transactional(rollbackFor = Exception.class)

@Transactional(rollbackFor = Exception.class)  // 任何异常都回滚
public void transfer(Account from, Account to, BigDecimal amount) {
    // ...
}

4. 数据库引擎不支持事务(如 MySQL 的 MyISAM)

原因:某些数据库引擎(如 MyISAM)不支持事务,即使加了 @Transactional 也不会生效。

解决方案
确保使用支持事务的引擎(如 InnoDB)

ALTER TABLE your_table ENGINE=InnoDB;

5. 事务传播行为设置不当

原因:如果传播行为设置为 Propagation.NOT_SUPPORTEDPropagation.NEVER,事务不会生效。

示例

@Transactional(propagation = Propagation.NOT_SUPPORTED)  // 不支持事务
public void logOperation(Log log) {
    logDao.save(log);  // 无事务
}

解决方案
合理设置传播行为(默认 REQUIRED

@Transactional(propagation = Propagation.REQUIRED)  // 默认值,支持事务
public void logOperation(Log log) {
    logDao.save(log);
}

6. 类未被 Spring 管理

原因:如果类没有 @Service@Component 等注解,@Transactional 不会生效。

示例

public class ExternalService {  // 没有 @Service/@Component
    @Transactional  // 无效
    public void process() {
        // ...
    }
}

解决方案
确保类被 Spring 管理

@Service
public class ExternalService {
    @Transactional  // 生效
    public void process() {
        // ...
    }
}

7. 多数据源未正确配置事务管理器

原因:多数据源环境下,如果没有为每个数据源配置事务管理器,事务可能失效。

解决方案
配置多数据源事务管理器

@Configuration
@EnableTransactionManagement
public class DataSourceConfig {
    @Bean
    @Primary
    public PlatformTransactionManager primaryTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public PlatformTransactionManager secondaryTransactionManager(DataSource secondaryDataSource) {
        return new DataSourceTransactionManager(secondaryDataSource);
    }
}

使用时指定事务管理器:

@Transactional("primaryTransactionManager")  // 指定事务管理器
public void primaryDbOperation() {
    // ...
}

8. 异常类型不匹配

原因:默认情况下,Spring事务只对RuntimeException和Error回滚,对受检异常(checked exception)不回滚。那什么是受检异常?受检异常(Checked Exceptions)是那些在编译时就必须被处理的异常,它们继承自Exception类但不继承RuntimeException类。以下是Java中常见的受检异常:

  1. IO相关异常
    IOException:输入输出操作失败的通用异常
    FileNotFoundException:试图打开不存在的文件
    EOFException:在输入过程中意外到达文件或流的末尾
    SocketException:底层协议有错误,如TCP错误
    MalformedURLException:URL格式不正确
  2. SQL相关异常
    SQLException:数据库访问错误或其他错误
    SQLTimeoutException:数据库操作超时
    SQLSyntaxErrorException:SQL语法错误
  3. 反射相关异常
    ClassNotFoundException:无法加载请求的类
    NoSuchMethodException:请求的方法不存在
    IllegalAccessException:对类、方法或字段的访问被拒绝
  4. 网络相关异常
    UnknownHostException:无法确定主机的IP地址
    ConnectException:连接服务器被拒绝
  5. 其他常见受检异常
    ParseException:解析字符串失败(如日期解析)
    CloneNotSupportedException:对象不支持克隆操作
    InterruptedException:线程被中断
    TimeoutException:阻塞操作超时

解决方法也很简单,添加rullbackFor: @Transactional(rollbackFor=Exception.class)
示例

@Service
public class FileService {
    @Transactional
    public void processFile() throws IOException {  // 受检异常
        // 文件处理逻辑
        throw new IOException("文件处理失败");  // 默认情况下不会导致事务回滚
    }
}

受检异常 vs 非受检异常
继承关系: 受检异常继承Exception但不继承RuntimeException,非受检异常继承RuntimeException或Error
处理要求: 受检异常必须捕获或声明抛出, 非受检异常不强制要求处理
示例: 受检异常IOException, SQLException,非受检异常NullPointerException, ArrayIndexOutOfBoundsException
使用场景: 受检异常可恢复的条件,调用者应该处理的情况,非受检异常程序错误,通常不应捕获


总结

失效原因 解决方案
方法非 public 改为 public 方法
自调用问题 拆分到不同类或使用 AopContext.currentProxy()
异常被捕获未抛出 重新抛出异常或配置 rollbackFor
数据库引擎不支持事务 使用 InnoDB 等支持事务的引擎
传播行为设置不当 使用 REQUIRED(默认)
类未被 Spring 管理 添加 @Service/@Component
多数据源未配置事务管理器 为每个数据源配置 PlatformTransactionManager
异常类型不匹配 添加 rollbackFor=Exception.class