Php 使用复合主键生成自动增量ID

Php 使用复合主键生成自动增量ID,php,mysql,sql,symfony,doctrine-orm,Php,Mysql,Sql,Symfony,Doctrine Orm,我想给一个实体(发票、订单、预订等)一个唯一的序列号,但仅在该年内唯一。因此,每年的第一张发票(或其他字段,如Customer)的起始id为1。这意味着可以有一个复合主键(year,id)或一个主键(即invoice_id)以及两个其他列,它们在一起是唯一的 我的问题:使用Doctrine2和Symfony2为对象提供自动生成的ID和另一个值的唯一组合的最佳方法是什么 复合密钥的原则限制 条令无法将自动生成的ID分配给具有复合主键()的实体: 具有复合密钥的每个实体都不能使用其他id生成器 而不

我想给一个实体(发票、订单、预订等)一个唯一的序列号,但仅在该年内唯一。因此,每年的第一张发票(或其他字段,如Customer)的起始id为1。这意味着可以有一个复合主键(year,id)或一个主键(即invoice_id)以及两个其他列,它们在一起是唯一的

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

复合密钥的原则限制

条令无法将自动生成的ID分配给具有复合主键()的实体:

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

手动设置序列号

所以我必须手动分配一个ID。为了做到这一点,我尝试寻找某一年中最高的ID,并给新实体ID+1。我怀疑这是最好的方法,即使是这样,我也没有找到最好的方法。因为我认为这是一个一般性的问题,我想避免这种情况,所以我开始问这个问题

香草选择:仅搭配MyISAM

我发现“香草”MySQL/MyISAM解决方案基于:

由于Doctrine2的局限性,这将不起作用,因此我正在寻找一种与此香草MySQL解决方案等效的doctrineORM

其他解决方案


InnoDB也有一个解决方案:

与您链接到的预插入触发器解决方案相当的ORM将是一个生命周期回调。你可以阅读更多关于他们的信息

一个天真的解决方案看起来是这样的

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();
        }
    }
}

#winces#vanilla选项的(可能)问题是在事务期间必须锁定整个表,这会导致并发的
INSERT
s失效。为什么要每年重复ID?实际的id值是没有意义的;如果要输出序列,请执行自联接和
COUNT()
。出于财务原因,发票必须具有序列号(每年从id=1开始)。它不必是主键的一部分,我会编辑我的问题。不,好的,是的,你可能需要一个循环,持续序列然后。。。您可能无法摆脱每年制作此系列的命运,但至少可以解决表锁定问题;将计数器存储在另一个表中(按年份键入),然后用存储过程或触发器(在它们自己的事务中)点击它。如果有人不提交他们的行,那么最终会出现间隙,但至少长时间运行的事务不会将其他人锁定在外。然后在这些列上只需要一个
唯一键
。这听起来是个好主意,但我不确定我是否真的理解它。我必须如何在Symfony2/ORM中实现它?@Clockwork Muse我知道你是SQL方面的专家。我想到了一个Symfony2/Doctrine实现我找到的普通解决方案。您是否同意或正在考虑其他计划?无法使其工作,但看起来像我需要的:)。谢谢实体\发票的外观如何?顺便说一句,
return[]
在这里给出了一个错误,我将其更改为
return array()
发票实体的唯一添加项是其序列号的单独整数属性(由setSequenceNum设置)。这不是唯一标识符,只是一个常规的旧整数。我还假设它在这里有一个year属性(getYear),但显然,您也可以在date列或其他任何列中执行相同的操作。短数组语法实际上是PHP5.4添加的(请参阅),因此您会遇到这个错误,因为您使用的是旧版本的PHP。我仍然无法让它工作。似乎后刷新没有被调用。你能提供更多的代码示例吗?你把这个类注册为事件订阅者了吗?请使用
@ORM\EntityListeners({})
查看.Yes。可以吗?
<?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();
        }
    }
}