Doctrine2 ORM выберите для обновления
Пожалуйста, не могли бы вы предложить мне какой-то подход, как реализовать SELECT FOR UPDATE
с доктриной?
Мне нужно прочитать какое-то значение счетчика, использовать его в коде PHP и немедленно увеличить значение, прежде чем кто-то другой (из другого процесса) использует это же значение.
3 ответа
Блокировка поддержки
В Doctrine 2 реализована поддержка блокировки для сущностей:
<?php
use Doctrine\DBAL\LockMode;
use Doctrine\ORM\OptimisticLockException;
$theEntityId = 1;
$expectedVersion = 184;
try {
$entity = $em->find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion);
// do the work
$em->flush();
} catch(OptimisticLockException $e) {
echo "Someone else has already changed this entity. Apply the changes again!";
}
Родной sql
Кроме того, вы можете сделать это с помощью execute raw SQL:
$em->getConnection()->exec('LOCK TABLES table_name WRITE;'); //lock for write access
а потом
$em->getConnection()->exec('UNLOCK TABLES;');
По-видимому, Doctrine 2 использует LOCK IN SHARED MODE с пессимистической блокировкой чтения для MySQL, которая отличается от SELECT FOR UPDATE.
Глядя на источники текущей стабильной версии, кажется, что в Doctrine нет собственного способа сделать это (я не уверен, почему команда Doctrine выбрала такой тип блокировки для MySQL).
Я использовал собственный SQL в качестве обходного пути, который можно сопоставить с традиционными сущностями, как это было бы с DQL:
<?php
$rsm = new ResultSetMappingBuilder($this->_em);
$rsm->addRootEntityFromClassMetadata('Model_Record_Delivery', 'u');
$query = $this->_em->createNativeQuery("SELECT * FROM delivery WHERE id = :id FOR UPDATE", $rsm);
$query->setParameter("id", $id);
$result = $query->getOneOrNullResult();
Обновить
Как отметил Бенджамин, PESSIMISTIC_WRITE - это то, что вы ищете.
С DQL
<?php
$query = $this->em->createQuery('SELECT e
FROM Application\Model\Entity\MyEntity e
WHERE e = :id');
$query->setParameter("id", $id);
$query->setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE);
Без DQL
<?php
$entity = $em->find('Application\Model\Entity\MyEntity', $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE);
Кроме того, вы должны использовать инструкцию внутри транзакции, чтобы заставить ее работать.
Предупреждение для всех, кто приходит сюда из Google.
Если вы используете блокировку Doctrine PESSIMISTIC_WRITE для существующей сущности,
то эта сущность не будет повторно загружена после блокировки.
Итак, этот код:
$entity = $this->em->find(Product::class, $id);
// use the product for some read only code
// Later, Need to update product
$this->em->lock($entity, LockMode::PESSIMISTIC_WRITE);
$entity->setStock($entity->getStock() - 1);
$this->em->flush();
Выполнит что-то похожее на следующий код в SQL
SELECT t0.id AS id_1, t0.stock AS stock_2 FROM products t0 WHERE t0.id = ?; -- First fetch
SELECT 1 FROM products t0 WHERE t0.id = ? FOR UPDATE; -- Pessimistic lock, no data fetched
UPDATE products SET stock = ? WHERE id = ?; -- Update using old data
Это дает тот же результат, что и вообще ничего не блокируя.
Вам нужно снова вручную получить объект, одновременно запрашивая блокировку:
$entity = $this->em->find(Product::class, $id);
// use the product for some read only code
// Need to update product
$this->em->find(Product::class, $entity->getId(), LockMode::PESSIMISTIC_WRITE); // You dont need the return value, doctrine will update all loaded entities
$entity->setStock($entity->getStock() - 1);
$this->em->flush();
Это единственный способ убедиться, что доктрина обновит свой кеш, а также сам объект сущности после получения блокировки.
Ни один
$em->lock()
, ни
$em->refresh()
здесь будет работать.