【问题标题】:Generate auto increment ID with composite primary key使用复合主键生成自增 ID
【发布时间】:2014-06-17 17:33:39
【问题描述】:

我想给一个实体(发票、订单、预订等)一个唯一的序列号,但仅在该年内唯一。因此,每年的第一张发票(或其他字段,例如客户)开始时的 id 为 1。这意味着可以有一个复合主键(年份,id)或一个主键(即 invoice_id)和另外两个列一起独一无二。

我的问题:使用 Doctrine2 和 Symfony2 为对象提供自动生成的 ID 和另一个值的唯一组合的最佳方法是什么?

复合键的原则限制

Doctrine 无法将自动生成的 ID 分配给具有复合主键 (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html) 的实体:

每个具有复合键的实体都不能使用其他的 id 生成器 比“分配”。这意味着 ID 字段必须有它们的值 在致电EntityManager#persist($entity)之前设置。

手动设置序号

所以我必须手动分配一个 ID。为了做到这一点,我试图寻找某一年的最高 ID 并给新实体 ID + 1。我怀疑这是最好的方法,即使是,我也没有找到最好的(干)的方式来做到这一点。由于我认为这是一个通用问题并且我想阻止XY-problem,因此我已经开始了这个问题。

普通选项:仅适用于 MyISAM

我找到了基于this answer 的“香草”MySQL/MyISAM 解决方案:

CREATE TABLE IF NOT EXISTS `invoice` (
  `year` int(1) NOT NULL,
  `id` mediumint(9) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`year`,`id`)
) ENGINE=MyISAM;

由于 Doctrine2 的限制,这不起作用,所以我正在寻找与这个 vanilla MySQL 解决方案等效的 Doctrine ORM。

其他解决方案

InnoDB 也有一个解决方案:Defining Composite Key with Auto Increment in MySQL

【问题讨论】:

  • #winces# vanilla 选项的(可能)问题是您必须在事务期间锁定整个表,这会杀死并发INSERT s。为什么要每年重复 id?一个实际的 id 值是没有意义的;如果你想输出一个序列,做一个自连接和COUNT()
  • 出于财务原因,发票必须有一个序列号(每年从 id=1 开始)。它不必是主键的一部分,我将编辑我的问题。
  • 不,好吧,是的,你可能确实需要一个循环的、持续的序列......你可能无法摆脱制作这个每年连续播放,但你至少可以解决表锁定问题;将计数器存储在另一个表中(按年份键),然后使用存储过程或触发器(在他们自己的事务中)对其进行命中。如果有人不提交他们的行,那么您最终会出现差距,但至少长期运行的事务不会将其他所有人拒之门外。那么你只需要在这些列上添加一个UNIQUE KEY
  • 这听起来是个好主意,但我不确定我是否真的理解它。我如何在 Symfony2/Doctrine ORM 中实现它?
  • @Clockwork-Muse 我知道您是 SQL 专家。我想到了我找到的 vanilla 解决方案的 Symfony2/Doctrine 实现。您同意还是正在考虑替代计划?

标签: php mysql sql symfony doctrine-orm


【解决方案1】:

您要链接到的预插入触发器解决方案的 ORM 等效项是生命周期回调。你可以阅读更多关于他们的信息here

一个简单的解决方案看起来像这样。

services.yml

services:
    invoice.listener:
        class: MyCompany\CompanyBundle\EventListener\InvoiceListener
        tags :
            - { name: doctrine.event_subscriber, connection: default }

InvoiceListener.php

<?php

namespace MyCompany\CompanyBundle\EventListener;

use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\PostFlushEventArgs;

use MyCompany\CompanyBundle\Entity\Invoice;

class InvoiceListener implements EventSubscriber {

    protected $invoices;

    public function getSubscribedEvents() {
        return [
            'onFlush',
            'postFlush'
        ];
    }

    public function onFlush(OnFlushEventArgs $event) {
        $this->invoices = [];
        /* @var $em \Doctrine\ORM\EntityManager */
        $em = $event->getEntityManager();
        /* @var $uow \Doctrine\ORM\UnitOfWork */
        $uow = $em->getUnitOfWork();

        foreach ($uow->getScheduledEntityInsertions() as $entity) {
            if ($entity instanceof Invoice) {
                $this->invoices[] = $entity;
            }
        }
    }

    public function postFlush(PostFlushEventArgs $event) {
        if (!empty($this->invoices)) {

            /* @var $em \Doctrine\ORM\EntityManager */
            $em = $event->getEntityManager();

            foreach ($this->invoices as $invoice) {
                // Get all invoices already in the database for the year in question
                $invoicesToDate = $em
                    ->getRepository('MyCompanyCompanyBundle:Invoice')
                    ->findBy(array(
                        'year' => $invoice->getYear()
                        // You could include e.g. clientID here if you wanted
                        // to generate a different sequence per client
                    );
                // Add your sequence number
                $invoice->setSequenceNum(count($invoicesToDate) + 1);

                /* @var $invoice \MyCompany\CompanyBundle\Entity\Invoice */
                $em->persist($invoice);
            }

            $em->flush();
        }
    }
}

【讨论】:

  • 不能让它工作,但看起来像我需要的:)。谢谢! Entity\Invoice 长什么样?顺便return []这里报错,我改成return array()
  • 对 Invoice 实体的唯一补充是其序列号的单独整数属性(由 setSequenceNum 设置)。这不是一个唯一标识符,只是一个常规的旧整数。我还假设它在这里有一个年份属性(getYear),但你显然可以对日期列或其他东西做同样的事情。短数组语法实际上是 PHP 5.4 的新增功能(请参阅 here),因此您会收到该错误,因为您使用的是旧版本的 PHP。
  • 我仍然无法正常工作。似乎没有调用 postFlush 。能否提供更多代码示例?
  • 您是否将此类正确注册为事件订阅者?请参阅documentation
  • 是的,使用@ORM\EntityListeners( {} )。可以吗?
猜你喜欢
  • 2017-10-14
  • 2011-01-01
  • 2019-05-04
  • 2011-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-27
相关资源
最近更新 更多