@Transactional 注解避坑指南

272 阅读4分钟

  在实际开发当中,大家都知道对数据库的更新、删除操作要确保最终一致性。在SpringBoot项目中,大家都知道使用@Transactional注解就能解决这个问题。但是大多数同学在使用上可能就是给方法标记 @Transactional,不会去关注事务是否有效、出错后事务是否正确回滚,也不会考虑复杂的业务代码中涉及多个子业务逻辑时,怎么正确处理事务。


本篇笔记主要从两个方面来讲述 @Transactional 的使用技巧

  • @Transactional的错误使用方式
  • @Transactional的正确使用方式

1. @Transactional 错误使用方式

1.1 privateprotected 方法上加 @Transactional

@Service
public class OrderServiceImpl implements OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Resource
    private OrderDao orderDao;

    @Override
    public void saveOrder(Order order) {
        log.info("保存订单");
        save(order);
    }

    @Transactional
    protected void save(Order order) {
        orderDao.insert(order);
        throw new RuntimeException();
    }
}
  • 事务不生效的原因:Spring 默认通过动态代理的方式实现 AOP,对目标方法进行增强,private、protected 方法无法被代理,Spring 自然也无法动态增强事务处理逻辑
  • @Transactional 生效原则:@Transactional应该标注在 public 方法上

1.2 public 方法上加 @Transactional

@Service
public class OrderServiceImpl implements OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Resource
    private OrderDao orderDao;

    @Override
    public void saveOrder(Order order) {
        log.info("保存订单");
        save(order);
    }

    @Transactional
    public void save(Order order) {
        orderDao.insert(order);
        throw new RuntimeException();
    }
}
  • 事务不生效的原因:Spring 利用 AOP 技术对方法进行增强,所以要调用增强过的方法必然是通过调用代理后的对象来调用该对象的增强方法
  • @Transactional 生效原则:必须通过代理过的类从外部调用目标方法才能生效

1.3 方法异常被捕获

@Service
public class OrderServiceImpl implements OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Resource
    private OrderDao orderDao;

    @Override
    @Transactional
    public void saveOrder(Order order) {
        log.info("保存订单");
        try {
        
            save(order);
        
        } catch (Exception e) {
            log.error("保存订单异常!", e);
        }
    }

    private void save(Order order) {
        orderDao.insert(order);
        throw new RuntimeException();
    }
}
  • 事务不生效的原因:@Transactional 标记的业务方法异常被捕获。此时事务是生效的,但是由于方法内 catch 了所有异常,异常无法从方法传播出去,事务自然无法回滚
  • @Transactional 生效原则:@Transactional 标记的方法不能 catch 异常

1.4 方法抛出了检查异常

  • 常见的检查异常有 IOExceptionSQLExceptionFileNotFoundException
@Service
public class OrderServiceImpl implements OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Resource
    private OrderDao orderDao;

    @Override
    @Transactional
    public void saveOrder(Order order) throws Exception {
        log.info("保存订单");
        save(order);
    }

    private void save(Order order) throws IOException {
        orderDao.insert(order);
        throw new IOException();
    }
}
  • 事务不生效的原因:@Transactional 标记的业务方法抛出了检查异常,Spring 认为发生受检查异常时业务流程还未完成,所以不会回滚事务
  • @Transactional 生效原则:@Transactional 标记的方法默认出现 RuntimeException(非受检异常)或 Error 时才会回滚事务

2. @Transactional 正确使用方式

2.1 正确使用方式一

@Service
public class OrderServiceImpl implements OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Resource
    private OrderDao orderDao;

    @Override
    @Transactional
    public void saveOrder(Order order) {
        log.info("保存订单");
        save(order);
    }

    private void save(Order order) {
        orderDao.insert(order);
        throw new RuntimeException();
    }
}

2.2 正确使用方式二

  • 如果你需要对部分检查异常也进行判断,那就需要需要捕获异常后手动回滚事务
@Service
public class OrderServiceImpl implements OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Resource
    private OrderDao orderDao;

    @Override
    @Transactional
    public void saveOrder(Order order) throws Exception {
        log.info("保存订单");
        try {

            save(order);

        } catch (IOException e) {
            log.error("出现了检查异常,手动回滚事务");
            //手动回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }

    private void save(Order order) throws IOException {
        orderDao.insert(order);
        throw new IOException();
    }
}
  • 如果你不想手动操作回滚事务,可以使用 @Transactional(rollbackFor = Exception.class)注解的rollbackFor = Exception.class 属性可以帮助你打破默认不回滚检查异常的限制
@Service
public class OrderServiceImpl implements OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Resource
    private OrderDao orderDao;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveOrder(Order order) throws Exception {
        log.info("保存订单");
        save(order);
    }

    private void save(Order order) throws IOException {
        orderDao.insert(order);
        throw new IOException();
    }
}

2.3 父级方法抛异常,子级方法事务也会回滚

@Service
public class OrderServiceImpl implements OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Resource
    private OrderDao orderDao;
    @Resource
    private PayOrderDao payOrderDao;

    @Override
    @Transactional
    public void saveOrder(Order order) throws Exception {
        log.info("保存订单");

        save(order);

        PayOrder payOrder = new PayOrder();
        payOrder.setOrderNo(order.getOrderNo());
        payOrder.setPayNo(System.currentTimeMillis() + "");
        payOrderDao.insert(payOrder);

        throw new RuntimeException();
    }

    private void save(Order order) {
        orderDao.insert(order);
    }
}
  • 这种调用方式 payOrderDao.insert(payOrder)orderDao.insert(order) 都会回滚

2.4 子方法抛异常,所有操作都回滚


@Service
public class OrderServiceImpl implements OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderServiceImpl.class);

    @Resource
    private OrderDao orderDao;
    @Resource
    private PayOrderDao payOrderDao;

    @Override
    @Transactional
    public void saveOrder(Order order) throws Exception {
        log.info("保存订单");

        save(order);
        insertPayOrder(order);
    }

    private void save(Order order) {
        orderDao.insert(order);
        // 抛出异常
        throw new RuntimeException();
    }

    private void insertPayOrder(Order order) {
        PayOrder payOrder = new PayOrder();
        payOrder.setOrderNo(order.getOrderNo());
        payOrder.setPayNo(System.currentTimeMillis() + "");
        payOrderDao.insert(payOrder);
    }
}