Home > Spring Boot > SpringBoot 数据库事务注解使用规范

SpringBoot 数据库事务注解使用规范
事务 java

数据库事务注解使用规范

事务的启动机制

在进入@Transactional注解方法后即获取数据库连接,待首次数据库操作时开启事务,方法结束后提交或回滚并归还连接。但当方法处理时间较长时候,由于方法不能快速结束,导致数据库连接池无法及时归还,极易出现应用耗尽数据库连接池问题,进而导致系统崩溃。

使用规范

  • 对大事务进行优化,针对内存耗时操作、远程http请求等非数据库操作,建议放到@Transactional注解方法的外部,避免过早或过长时间占用数据库连接,减少数据库大事务风险,避免客户端数据库连接池连接不足的问题
  • 事务注解@Transactional禁止放在java类维度,避免无谓的事务封装
  • 禁止在纯查询操作方法上使用事务注解
  • 明确事务注解仅对public方法有效,protected、private方法上使用@Transactional均无效
  • 并且非事务方法调用同类内部采用 @Transactional 修饰的方法时,事务不会生效,异常不会触发回滚
  • 严禁在事务方法内捕获异常而不抛出,如采用@Transactional修饰方法
  • 当研发自行添加了try/catch捕获异常且未抛出异常时,@Transactional 无法自动回滚

避免过早或过长时间占用数据库连接

对大事务进行优化,针对内存耗时操作、远程http请求等非数据库操作,建议放到@Transactional注解方法的外部,避免过早或过长时间占用数据库连接,减少数据库大事务风险,避免客户端数据库连接池连接不足的问题

反例

问题:整个方法被 @Transactional 包裹,数据库连接从开始到结束一直被占用,即使中间没有 DB 操作。可能导致连接池耗尽。

@Service
public class OcrService {

    @Autowired
    private OcrServiceMapper ocrServiceMapper;

    @Autowired
    private RestTemplate restTemplate;

    @Transactional
    public void insert(OcrPojo ocrPojo) {
      
        // 1. 耗时的内存操作(不应该在事务中)
        heavyInMemoryProcessing();

        // 2. 远程调用(不应该在事务中)
        restTemplate.postForEntity("https://api.example.com/getOcr", ocrId, String.class);

    	// 3. 保存 ocr 信息(DB 操作)
        ocrServiceMapper.insert(ocrPojo);

        // 事务直到这里才提交,连接长时间被占用 ❌
    }  
}

正例

拆分事务,非 DB 操作移出事务

@Service
public class OcrService {

    @Autowired
    private DemoService demoService;

    @Autowired
    private RestTemplate restTemplate;

    public void buildInsert(OcrPojo ocrPojo) {
      
        // 第一步:非事务操作(内存处理 + 远程调用)
        heavyInMemoryProcessing();
        restTemplate.postForEntity("https://api.example.com/getOcr", ocrId, String.class);

        // 第二步:在独立事务中保存ocr
        demoService.insert(ocrPojo);
    }
}

@Service
public class DemoService {
    
    @Autowired
    private OcrServiceMapper ocrServiceMapper
    
    @Transactional
    public void insert(OcrPojo ocrPojo) {
        ocrServiceMapper.insert(ocrPojo);
    }
}

禁止类维度事务注解

事务注解@Transactional禁止放在java类维度,避免无谓的事务封装

反例

问题:@Transactional 注解不应随意加在 Java 类的维度(即类级别)上,尤其是当类中包含多个方法,且并非所有方法都需要事务时。这样做容易导致不必要的事务封装,进而引发性能问题或意外的事务传播行为。

@Service
@Transactional
public class OcrService {

    public void insert(OcrPojo ocrPojo) {
        // 保存ocr(需要事务)
        // ...
    }

    public void demo() {
        // 纯远程调用,无需事务 ❌ 却被事务包裹
          restTemplate.post(...);
    }

    public String selectOcrById(String id) {
        // 查询ocr操作,无需事务 ❌ 却被事务包裹
        return ocrInfo;
    }
}

正例

@Transactional 添加到具体需要事务的方法上

@Service
public class OcrService {

    @Transactional
    public void insert(OcrPojo ocrPojo) {
        // 保存ocr(需要事务)
        // 只有这个方法需要完整事务
    }

    public void demo() {
          // 无事务,避免占用数据库连接
          restTemplate.post(...);
    }

    public String selectOcrById(String id) {
        // 无事务,避免占用数据库连接
        return ocrInfo;
    }
}

禁止在纯查询操作方法上使用事务注解

反例

问题:

  • 纯查询操作被纳入事务,占用数据库连接;
  • 在高并发场景下,可能导致连接池耗尽;
  • 事务日志记录、事务管理器介入,增加系统开销。
@Service
public class OrderService {

    @Transactional
    public Order findById(Long id) {
        return orderRepository.findById(id);
    }
}

正例

避免占用连接

@Service
public class OrderService {

    public Order findById(Long id) {
        return orderRepository.findById(id);
    }
}

明确事务注解仅对public方法有效

@Transactional 注解仅对 public 方法有效,这是 Spring 基于代理机制实现事务控制的一个重要限制

Spring 的 @Transactional 是通过 AOP 代理(JDK 动态代理或 CGLIB)实现的。当方法调用通过代理对象进入时,Spring 才能拦截并开启事务上下文。如果方法不是 public 的,代理机制将无法正常工作,导致事务不生效。

@Service
public class OrderService {

    // ❌ protected 方法:事务不生效
    @Transactional
    protected void updateOrderProtected(Order order) {
        orderRepository.save(order);
    }

    // ❌ private 方法:事务不生效,且代理完全无法拦截
    @Transactional
    private void logOperation() {
        // 记录日志操作
    }

    // ❌ package-private(默认访问级别):事务不生效
    @Transactional
    void sendNotification() {
        restTemplate.post(...);
    }
}

内部自调用也无法触发事务

非事务方法调用同类内部采用 @Transactional 修饰的方法时,事务不会生效,异常不会触发回滚

Spring 的 @Transactional 是通过 AOP 代理实现的。只有当方法调用来自类的外部(即通过代理对象调用)时,事务拦截器才会生效。如果是在类内部通过 this 直接调用,会绕过代理,导致事务失效

反例

@Service
public class OrderService {

    @Transactional
    public void createOrder(Order order) {
        orderRepository.save(order);
    }

    // 非事务方法调用同类中的事务方法
    public void processOrder(Order order) {
        createOrder(order); // ❌ 通过 this.createOrder() 直接调用,事务不生效
    }
}

正例

推荐做法:将事务方法拆分到不同类中,职责分离,事务控制更清晰。

@Service
public class OrderService {

    @Autowired
    private DemoService demoService;

    public void processOrder(Order order) {
		 // ✅ 跨类调用,事务生效
        demoService.createOrder(order);
    }
}

@Service
class DemoService {

    @Transactional
    public void createOrder(Order order) {
        orderRepository.save(order);
    }
}

严禁在事务方法内捕获异常而不抛出

严禁在 @Transactional 修饰的事务方法中捕获异常后不重新抛出,否则会导致 Spring 事务无法感知异常,从而无法触发回滚,造成数据不一致。

Spring 的声明式事务(@Transactional)是基于代理的。它通过拦截方法的异常抛出来判断是否需要回滚。如果异常被 try-catch 捕获并“吞掉”(即不抛出),Spring 会认为方法执行成功,从而提交事务,即使内部出错了

反例

捕获异常但未抛出,即使 paymentService.charge() 抛出异常,由于被 try-catch 捕获且未重新抛出,Spring 会认为方法执行成功,事务正常提交,但实际业务已失败,导致数据不一致。

@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private PaymentService paymentService;

    @Transactional
    public void createOrder(Order order) {
        orderRepository.save(order);

        try {
            paymentService.charge(order.getAmount());
        } catch (Exception e) {
            // ❌ 错误:捕获了异常但没有抛出,事务不会回滚
            log.error("支付失败:", e);
            // 没有 throw,事务正常提交
        }
    }
}

正确做法一

捕获后手动回滚

如果你必须捕获异常并处理,应主动标记事务回滚

@Transactional
public void createOrder(Order order) {
    orderRepository.save(order);

    try {
        paymentService.charge(order.getAmount());
    } catch (Exception e) {
        log.error("支付失败:", e);
        // ✅ 抛出异常,触发回滚
        throw new RuntimeException("支付失败,事务将回滚", e); 
    }
}

正确做法二

使用 @Transactional(rollbackFor = Exception.class) 明确回滚策略

默认情况下,Spring 事务只对 RuntimeExceptionError 自动回滚,对 checked exception(如 IOException)不回滚。使用 rollbackFor 可以扩展回滚范围。

@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) throws PaymentException {
    orderRepository.save(order);
    // 抛出 checked exception 也能回滚
    paymentService.charge(order.getAmount()); 
}

正确做法三

捕获异常后选择性处理,再抛出自定义异常

@Transactional(rollbackFor = BusinessException.class)
public void createOrder(Order order) {
    orderRepository.save(order);
    try {
        paymentService.charge(order.getAmount());
    } catch (PaymentException e) {
        log.error("支付异常", e);
        // ✅ 抛出被事务识别的异常
        throw new BusinessException("支付失败", e); 
    }
}

正确做法四

不想抛出异常,可以手动回滚事务

@Transactional
public void createOrder(Order order) {
    orderRepository.save(order);

    try {
        paymentService.charge(order.getAmount());
    } catch (Exception e) {
        log.error("支付失败,手动回滚", e);
        // 手动回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 
    }
}