数据库事务注解使用规范
事务的启动机制
在进入@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 事务只对 RuntimeException 和 Error 自动回滚,对 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();
}
}