Symfony中数据库事务必须在服务层用连接对象手动控制:beginTransaction()、commit()、rollback(),配合try/catch确保异常时回滚,跨Repository操作天然共享同一事务。

在 Symfony 2(及后续兼容版本)中做数据库事务处理,核心目标就一个:让多步写操作变成不可分割的“原子动作”——全成或全退。这不是可选项,而是涉及订单、库存、账户变动等关键业务时的必选项。
事务必须放在服务层,不能写在控制器里
控制器只负责接收请求、调用服务、返回响应。把 beginTransaction() 或 flush() 塞进控制器,会导致逻辑混杂、无法复用、难以测试,还容易漏掉回滚。正确做法是:
- 新建一个服务类(如 OrderService),注入 EntityManagerInterface
- 所有数据库变更操作(persist、remove、DQL 更新等)都在这个服务方法内完成
- 控制器只调用类似
$this->orderService->createOrder($data)这样的单一方法
手动控制事务边界:用连接对象,别用 EntityManager
新版 Doctrine 已弃用 $em->beginTransaction()。必须通过连接对象操作:
- 开启事务:
$em->getConnection()->beginTransaction() - 提交:
$em->getConnection()->commit() - 回滚:
$em->getConnection()->rollback() -
$em->flush()只是把内存变更同步到 SQL 队列,不等于提交;它可以在事务内多次调用,但真正落库由 commit 触发
异常路径必须显式回滚,且不能吞掉错误
没 catch 就直接 commit,一旦中间出错,事务会卡住,连接可能被占用甚至泄漏。可靠写法是:
- 用 try/catch 包裹全部业务逻辑
- try 结尾必须调用
commit() - catch 中第一件事就是
rollback(),然后 重新 throw 异常 或转为业务异常(如throw new OrderCreationException(...)) - 不要写
catch (\Exception $e) { $em->rollback(); }后就静默返回——上游无感知,问题难排查
跨 Repository 操作天然共享同一事务上下文
只要共用同一个 EntityManager 实例(默认是单例),无论你从 ProductRepository、OrderRepository 还是原生 DQL 查询数据,所有 persist/flush 都自动纳入当前连接的事务中。不需要额外协调,也不用传 Connection 对象。
比如创建订单 + 扣库存 + 记日志(写入另一张表),只要都在同一个 service 方法的事务块里,就天然具备原子性。

















