Doctrine: ON DUPLICATE KEY UPDATE

后端 未结 9 1060
有刺的猬
有刺的猬 2020-12-18 19:43

How can I write an INSERT doctrine query with option ON DUPLICATE KEY UPDATE?

相关标签:
9条回答
  • In case this helps, you can extend the query builder to append arbitrary SQL (obviously, this may not work across PDO engines):

    class MyQB extends QueryBuilder {
    
        private $append = '';
    
        /**
         * {@inheritdoc}
         */
        public function getSQL() {
            return parent::getSQL() . $this->append;
        }
    
        /**
         * Append raw SQL to the output query
         *
         * @param string $sql SQL to append. E.g. "ON DUPLICATE ..."
         *
         * @return self
         */
        public function appendSql($sql) {
            $this->append = $sql;
            return $this;
        }
    }
    
    0 讨论(0)
  • 2020-12-18 20:19

    I had the same problem and after investigating a bit it looks like Doctrine doesn't do it. My solution was to do a findBy before my insert to see if any records exist with the unique fields. If this returns an entity then I update that entity and persist it instead of creating a new entity to persist.

    If you are concerned about performance then this is not ideal as we are doing a select before every insert. However since Doctrine is database agnostic it is the only alternative to locking yourself to MySQL. It's one of those tradeoffs: do you want performance or portability.

    0 讨论(0)
  • 2020-12-18 20:22

    You can't. It's not supported by Doctrine right now.

    What you could do is to imitate what MySQL does by checking if the entity exists and update/create it accordingly:

    $em = $this->getEntityManager();
    
    // Prevent race conditions by putting this into a transaction.
    $em->transactional(function($em) use ($content, $type) {
      // Use pessimistic write lock when selecting.
      $counter = $em->createQueryBuilder()
        ->select('MyBundle:MyCounter', 'c')
        ->where('c.content = :content', 'c.type = :type')
        ->setParameters(['content' => $content, 'type' => $type])
        ->setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE);
        ->getQuery()
        ->getResult()
      ;
    
      // Update if existing.
      if ($counter) {
        $counter->increase();
      } else {
        // Create otherwise.
        $newCounter = new Counter($content, $type, 1);
        $em->persist($newCounter);
      }
    });
    

    If the record exists PESSIMISTIC_WRITE makes sure that it's not updated by anyone (e.g., other threads) while we're updating it.

    Although you need to check for the entity's existence on every update, it's a simple reproduction of "update if existing and create if not".

    As pointed out in the comments this does not prevent a race condition if the record doesn't exist: If a row with the same key(s) gets inserted between the select and the insert you're running into a duplicate key exception.

    But given the constraints that this needs to be DB independent and thus written using Doctrine and not using native SQL it may help in some cases.

    References:

    • Database Administrators: Putting a Select statement in a transaction
    • MySQL Reference Manual: Locking Reads (SELECT ... FOR UPDATE)
    • Doctrine 2 ORM documentation: Transactions and Concurrency
    • Stackoverflow: Doctrine2 ORM select for update
    0 讨论(0)
提交回复
热议问题