
关于事务应该放在Controller层还是Service层,结论很明确:事务必须、也只能放在Service层。
虽然在Controller层添加@Transactional注解在技术上是可行的,但这仅仅是“技术允许”,而非“工程正确”。这种做法混淆了架构各层的核心职责。
一、为什么事务不该放在Controller层?
在实际项目中,你或许见过下面这种写法:
@Transactional
@PostMapping("/order/create")
public void create(){
orderMapper.insert(order);
stockMapper.reduce(stock);
}
这种做法的主要问题不在于“能不能运行”,而在于严重的职责错位:
- Controller层错误地承担了维护业务一致性的责任。
- 接口层与事务机制产生了强耦合。
- 代码后期几乎无法被其他场景复用。
试想,当“创建订单”这个业务逻辑需要被以下方式调用时,你会怎么做?
结果往往是:要么复制粘贴整段代码,要么开始在Controller里进行各种别扭的相互调用。将事务写在Controller层,本质上是在“锁死”业务的入口,使得业务逻辑与特定的网络请求深度绑定,违反了分层架构的设计原则。
二、事务写在Service层,才是正确的职责分工
正确的做法是将事务边界定义在Service层,架构清晰明了:
@Service
public class OrderService {
@Transactional
public void createOrder(){
orderMapper.insert(order);
stockMapper.reduce(stock);
}
}
此时,Controller层的职责变得单一而纯粹——仅仅是接收请求、调用服务、返回响应:
@PostMapping("/order/create")
public void create(){
orderService.createOrder();
}
这种分工带来的好处是确定且显著的:
- 事务语义清晰:事务是业务逻辑的一部分,其边界由业务方法自然定义。
- 业务高度可复用:
createOrder()方法可以被任意Controller、Job或Listener调用,无需关心调用方的具体形式。
- 调用方式不受限:业务逻辑不再依赖于特定的接入方式(如HTTP)。
事务本质上是业务逻辑的一部分,用以保证业务操作的原子性,而不是对某个特定接口的装饰。 这种关于分层架构与职责分离的设计思想,是构建可维护后端系统的基石。
三、一个经常被忽略的关键点
即使你将事务注解正确地放在Service层,仍然可能遇到事务“失效”的情况,例如下面这种同类方法内部调用:
public void methodA(){
methodB(); // 事务不会生效
}
@Transactional
public void methodB(){
}
这失效的原因只有一个:在同一个类内部的方法调用,不会经过Spring的AOP代理机制,因此@Transactional注解不会被拦截处理。
这也是为什么我们强调:
- 事务注解必须加在“对外提供调用的入口方法”上。
- 在Service层中应避免复杂的自调用。
许多所谓的“事务失效”问题,根源并非注解本身,而是代码的层次与调用关系没有设计好。深入理解Spring的代理机制和事务管理原理,是解决这类问题的关键。
总结为一句话:事务是业务逻辑的边界,而非接口层的装饰。将其牢固地置于Service层,是保障代码清晰、可复用和易维护的基本原则。

希望这篇关于事务层次设计的探讨对你有帮助。如果你有更多关于架构或Spring的疑问,欢迎到云栈社区与其他开发者一起交流探讨。