Transactions & Locking
This content is for v1.0. Switch to the latest version for up-to-date documentation.
Use transactions and row-level locks when writes must be consistent across multiple queries.
Transactional wrapper
Section titled “Transactional wrapper”$entityManager->transactional(function (EntityManager $em) use ($entity) { $em->persist($entity); $em->flush();
return $entity;});The wrapper commits when the callback returns and rolls back when the callback throws.
Manual control
Section titled “Manual control”beginTransaction()starts a transaction.commit()flushes and commits.rollback()rolls back pending database work.
Manual control is useful when a command needs to demonstrate intermediate failure states or lock behavior.
Locking
Section titled “Locking”Use lock() on the query builder for SELECT ... FOR UPDATE:
$stock = $entityManager ->createQueryBuilder(StockLock::class) ->where('product_id', $productId) ->lock() ->getSingleResult();An inventory-decrement flow locks stock rows before decrementing:
$stock = $entityManager ->createQueryBuilder(StockLock::class) ->where('product_id', $productId) ->lock() ->getSingleResult();Common pitfalls
Section titled “Common pitfalls”- Calling
lock()outside an active transaction raises a transaction-required error. - Acquire multiple locks in a deterministic order to reduce deadlock risk.
- Keep transaction callbacks small; long-running work should happen before or after the transaction where possible.
See also
Section titled “See also”Pessimistic locking (SELECT ... FOR UPDATE) blocks other writers for the duration of a transaction. Contrast with Optimistic Locking, which never locks and instead detects conflicts at write time.