使用复合主键生成自增ID

5
我希望给一个实体(发票、订单、预订等)分配一个唯一的序列号,但只在该年内唯一。因此,每年的第一张发票(或其他字段,例如客户)开始时的ID为1。这意味着可以有一个复合主键(年份、ID)或一个主键(即invoice_id)和另外两个唯一的列。
我的问题是:使用Doctrine2和Symfony2,给对象分配自动生成的ID和另一个值的唯一组合的最佳方法是什么? 复合键上的Doctrine限制 Doctrine无法为具有复合主键的实体分配自动生成的ID(http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html):

每个具有复合键的实体都不能使用“ASSIGNED”以外的ID生成器。这意味着在调用EntityManager#persist($entity)之前必须设置ID字段的值。

手动设置序列号 所以我必须手动分配一个ID。为了做到这一点,我尝试查找特定年份中最高的ID,并将新实体分配为该ID + 1。我怀疑这不是最好的方法,即使是最好的方法,我也没有找到最好的(DRY)方法来实现它。由于我认为这是一个通用问题,并且我想防止 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的限制,这种方法行不通,因此我正在寻找一种Doctrine ORM等效于这个原始MySQL解决方案的方法。
其他解决方案:
对于InnoDB也有解决方案:在MySQL中定义带自增复合键

发票必须有一个序列号(每年从id = 1开始)以满足财务要求。它不必是主键的一部分,我会编辑我的问题。 - Stephan Vierkant
1
不,好的,是的,你可能确实需要一个循环持久化序列...... 你可能无法避免每年生成一个序列,但你至少可以解决表锁问题;将计数器存储在另一个表中(按年键入),然后使用存储过程或触发器(在其自己的事务内)访问它。如果有人没有提交他们的行,则会出现间隙,但至少长时间运行的事务不会将其他人锁定。然后你只需要在这些列上使用 UNIQUE KEY - Clockwork-Muse
@Clockwork-Muse,我看到您是SQL方面的专家。我正在考虑使用Symfony2/Doctrine实现我找到的基本解决方案。您同意还是有其他替代方案? - Stephan Vierkant
触发器/函数/约束通常仅在数据库端创建,因此从ORM的角度来看,它无法识别。试图在应用程序端实现这一点很可能会以失败告终。我不知道如何在这些ORM的上下文中处理这个问题。 - Clockwork-Muse
但是您认为上述基本解决方案是一个好主意,并且寻找ORM等效方案是一个好主意吗? - Stephan Vierkant
显示剩余2条评论
1个回答

3

你贴出的有关预插入触发器解决方案的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();
        }
    }
}

无法使其工作,但看起来就像我需要的 :). 谢谢!Entity\Invoice 是什么样子?顺便说一下,在这里“return []”会出错,我将其更改为“return array()”。 - Stephan Vierkant
发票实体的唯一添加将是一个单独的整数属性,用于其序列号(由setSequenceNum设置)。这不是唯一标识符,只是一个普通的整数。我还假设它在这里有一个年份属性(getYear),但你显然可以使用日期列或其他内容来完成相同的操作。短数组语法实际上是PHP 5.4的新增功能(请参见此处),因此您会因为使用旧版本的PHP而出现错误。 - Peter
我仍然无法让它工作。似乎postFlush没有被调用。你能提供更多的代码示例吗? - Stephan Vierkant
没问题。很高兴能帮助到您 :) - Peter
我喜欢这个工作方式 - 尽管每次添加新记录之前都必须查询序列号,这是否会带来显著的“成本”?无法通过Doctrine添加自增复合键似乎是拼图中相当重要的缺失部分。 - Bendy
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接