当前位置: 首页 > 知识库问答 >
问题:

ORM原则:使用由外键组成的复合主键保持集合

蒋岳
2023-03-14

我猜这是一个教义错误(我在JIRA问题跟踪器上提交了一个问题),但万一只是用户错误,我决定在这里发布它。

使用由2个外键和一个元数据字段组成的复合主键在联接表中持久化实体集合在某些情况下会失败。代码是以这里的指令为基础的:条令文件

调试日志:

[2013-12-31 11:48:48] app.INFO: GPA ID PRESAVE IN CONTROLLER:9 [] []
[2013-12-31 11:48:48] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:7 [] []
[2013-12-31 11:48:48] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:3 [] []
[2013-12-31 11:48:48] app.INFO: GPA ID PRESAVE IN CONTROLLER:9 [] []
[2013-12-31 11:48:48] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:6 [] []
[2013-12-31 11:48:48] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:4 [] []
[2013-12-31 11:48:48] doctrine.DEBUG: "START TRANSACTION" [] []
[2013-12-31 11:48:48] doctrine.DEBUG: INSERT INTO gpa_assessment_value (point_value, grade_point_average_id, assessment_id) VALUES (?, ?, ?) {"1":3,"2":"9","3":"7"} []
[2013-12-31 11:48:48] doctrine.DEBUG: UPDATE gpa_assessment_value SET point_value = ? WHERE grade_point_average_id = ? AND assessment_id = ? [4,9,6] []
[2013-12-31 11:48:48] doctrine.DEBUG: "COMMIT" [] []

失败:当外键%1在集合中的项之间相同,并且外键%2小于任何现有外键%2时,工作单元尝试插入现有实体,但不对新实体进行操作。

>

  • 示例:GPA“add val Over”存在且具有评估值{“assessment”:8,“value”:2}我们将尝试添加一个新的评估值,其中assessment_id

    请求有效负载:{“name”:“Add val Overse”,“Courses”:[],“AssessmentValues”:[{“Assessment”:6,“Value”:4},{“Assessment”:8,“Value”:2}]}

    调试日志:

    [2013-12-31 11:53:59] app.INFO: GPA ID PRESAVE IN CONTROLLER:10 [] []
    [2013-12-31 11:53:59] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:8 [] []
    [2013-12-31 11:53:59] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:2 [] []
    [2013-12-31 11:53:59] app.INFO: GPA ID PRESAVE IN CONTROLLER:10 [] []
    [2013-12-31 11:53:59] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:6 [] []
    [2013-12-31 11:53:59] app.INFO: PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:4 [] []
    [2013-12-31 11:53:59] doctrine.DEBUG: "START TRANSACTION" [] []
    [2013-12-31 11:53:59] doctrine.DEBUG: INSERT INTO gpa_assessment_value (point_value, grade_point_average_id, assessment_id) VALUES (?, ?, ?) {"1":2,"2":"10","3":"8"} []
    [2013-12-31 11:53:59] doctrine.DEBUG: "ROLLBACK" [] []
    [2013-12-31 11:53:59] request.CRITICAL: Uncaught PHP Exception Doctrine\DBAL\DBALException: "An exception occurred while executing 'INSERT INTO gpa_assessment_value (point_value, grade_point_average_id, assessment_id) VALUES (?, ?, ?)' with params [2, "10", "8"]:
    SQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint "gpa_assessment_value_pkey"
    CREATE TABLE assessment
    (
        id       bigserial NOT NULL,
        scale_id bigint    NOT NULL,
        title    varchar   NOT NULL,
        passing  boolean   NOT NULL,
        rank     int,
    
        PRIMARY KEY (id)
    );
    
    CREATE TABLE assessment_scale
    (
        id   bigserial NOT NULL,
        name varchar   NOT NULL,
    
        PRIMARY KEY (id)
    );
    -- ...
    
    CREATE TABLE grade_point_average
    (
        id                         bigserial       NOT NULL,
        name                       varchar         NOT NULL,
        additional_credit_allowance numeric(4, 2),
    
        PRIMARY KEY (id)
    );
    
    -- ...
    
    CREATE TABLE gpa_assessment_value
    (
        grade_point_average_id bigint        NOT NULL,
        assessment_id          bigint        NOT NULL,
        point_value            numeric(4, 2) NOT NULL,
    
        PRIMARY KEY (assessment_id, grade_point_average_id),
        FOREIGN KEY (assessment_id) REFERENCES assessment,
        FOREIGN KEY (grade_point_average_id) REFERENCES grade_point_average
    );
    
    
    namespace MyApp\Model;
    
    use Doctrine\ORM\Mapping\Entity;
    use Doctrine\ORM\Mapping\Id;
    use Doctrine\ORM\Mapping\GeneratedValue;
    use Doctrine\ORM\Mapping\Column;
    //...
    use Doctrine\Common\Collections\Collection;
    use Doctrine\Common\Collections\ArrayCollection;
    use MyApp\Util\ConstructorArgs;
    use MyApp\Model\GradePointAverage\AssessmentValue;
    // ...
    
    /**
     * @Entity("MyApp\Repository\GradePointAverageRepository")
     */
    class GradePointAverage
    {
        use ConstructorArgs;
    
        /**
         * @Id
         * @GeneratedValue
         * @Column(type="bigint")
         *
         * @var int
         */
        private $id;
    
        // ...
    
        /**
         * @OneToMany(targetEntity="MyApp\Model\GradePointAverage\AssessmentValue", mappedBy="gradePointAverage", cascade="persist")
         *
         * @var Collection
         */
        private $assessmentValues;
    
        // ...
    
        /**
         * @param array $args
         */
        public function __construct(array $args = [])
        {
            $this->assessmentValues = new ArrayCollection;
            // ...
            $this->handleArgs($args);
        }
    
        // ...
    
        /**
         * @return Collection
         */
        public function getAssessmentValues()
        {
            return $this->assessmentValues;
        }
    
        /**
         * @param ArrayCollection $assessmentValues
         */
        public function setAssessmentValues(ArrayCollection $assessmentValues)
        {
            $this->assessmentValues = $assessmentValues;
        }
    
        /**
         * @param AssessmentValue $assessmentValue
         */
        public function addAssessmentValue(AssessmentValue $assessmentValue)
        {
            $this->assessmentValues->add($assessmentValue);
        }
    
        /**
         * @param AssessmentValue $assessmentValue
         */
        public function removeAssessmentValue(AssessmentValue $assessmentValue)
        {
            $this->assessmentValues->removeElement($assessmentValue);
        }
    
        // ...
    }
    
    namespace MyApp\Model\GradePointAverage;
    
    use Doctrine\ORM\Mapping\Entity;
    use Doctrine\ORM\Mapping\Table;
    use Doctrine\ORM\Mapping\Column;
    use Doctrine\ORM\Mapping\Id;
    use Doctrine\ORM\Mapping\GeneratedValue;
    use Doctrine\ORM\Mapping\ManyToOne;
    use Doctrine\ORM\Mapping\JoinColumn;
    use MyApp\Model\GradePointAverage;
    use MyApp\Model\Assessment;
    use MyApp\Util\ConstructorArgs;
    
    /**
     * @Entity("MyApp\Repository\GradePointAverage\AssessmentValueRepository")
     * @Table("gpa_assessment_value")
     */
    class AssessmentValue
    {
        use ConstructorArgs;
    
        /**
         * @Id
         * @ManyToOne(targetEntity="MyApp\Model\GradePointAverage")
         */
        private $gradePointAverage;
    
        /**
         * @Id
         * @ManyToOne(targetEntity="MyApp\Model\Assessment")
         */
        private $assessment;
    
        /**
         * @Column("point_value")
         *
         * @var float
         */
        private $value;
    
        /**
         * @param array $args
         */
        public function __construct(array $args = [])
        {
            $this->handleArgs($args);
        }
    
        /**
         * @return GradePointAverage
         */
        public function getGradePointAverage()
        {
            return $this->gradePointAverage;
        }
    
        /**
         * @param GradePointAverage $gradePointAverage
         */
        public function setGradePointAverage(GradePointAverage $gradePointAverage)
        {
            $this->gradePointAverage = $gradePointAverage;
        }
    
        /**
         * @return Assessment
         */
        public function getAssessment()
        {
            return $this->assessment;
        }
    
        /**
         * @param Assessment $assessment
         */
        public function setAssessment(Assessment $assessment)
        {
            $this->assessment = $assessment;
        }
    
        /**
         * @return float
         */
        public function getValue()
        {
            return $this->value;
        }
    
        /**
         * @param float $value
         */
        public function setValue($value)
        {
            $this->value = $value;
        }
    
        /**
         * @return AssessmentScale
         */
        public function getAssessmentScale()
        {
            return $this->assessment->getScale();
        }
    }
    
    namespace MyApp\Model;
    
    use Doctrine\ORM\Mapping\Entity;
    use Doctrine\ORM\Mapping\Id;
    use Doctrine\ORM\Mapping\GeneratedValue;
    use Doctrine\ORM\Mapping\Column;
    use Doctrine\ORM\Mapping\ManyToOne;
    use MyApp\Model\Assessment\Scale;
    use MyApp\Util\ConstructorArgs;
    
    /**
     * @Entity("MyApp\Repository\AssessmentRepository")
     */
    class Assessment
    {
        use ConstructorArgs;
    
        /**
         * @Id
         * @GeneratedValue
         * @Column(type="bigint")
         *
         * @var int
         */
        private $id;
    
        // ...
    
        /**
         * @param array $args
         */
        public function __construct(array $args = [])
        {
            $this->handleArgs($args);
        }
    
        /**
         * @return int
         */
        public function getId()
        {
            return $this->id;
        }
    
        // ...
    }
    
    namespace MyApp\Repository;
    
    use Doctrine\ORM\EntityRepository;
    // ...
    use MyApp\Model\GradePointAverage;
    
    class GradePointAverageRepository extends BaseRepository implements GradePointAverageRepositoryInterface
    {
        // ...
    
        /**
         * @param GradePointAverage $gradePointAverage
         */
        public function save(GradePointAverage $gradePointAverage)
        {
            $this->getEntityManager()->persist($gradePointAverage);
            $this->getEntityManager()->flush();
        }
    }
    
    namespace MyApp\Repository\GradePointAverage;
    
    use Doctrine\ORM\EntityRepository;
    use MyApp\Model\GradePointAverage\AssessmentValue;
    
    class AssessmentValueRepository extends EntityRepository
    {
        /**
         * @param AssessmentValue $assessmentValue
         */
        public function save(AssessmentValue $assessmentValue)
        {
            $this->getEntityManager()->persist($assessmentValue);
            $this->getEntityManager()->flush();
        }
    }
    
    namespace MyApp\Manager;
    
    use InvalidArgumentException;
    use Symfony\Component\Validator\ValidatorInterface;
    use JMS\DiExtraBundle\Annotation\Service;
    use JMS\DiExtraBundle\Annotation\InjectParams;
    use JMS\SecurityExtraBundle\Annotation\PreAuthorize;
    use Knp\Component\Pager\Pagination\PaginationInterface;
    use MyApp\Repository\GradePointAverageRepository;
    use MyApp\PaginationFactory\GradePointAveragePaginationFactoryInterface;
    use MyApp\Model\GradePointAverage;
    
    /**
     * @Service("grade_point_average_manager")
     */
    class GradePointAverageManager
    {
        /**
         * @var GradePointAverageRepository
         */
        private $gradePointAverageRepository;
    
        /**
         * @var GradePointAveragePaginationFactoryInterface
         */
        private $gradePointAveragePaginationFactory;
    
        /**
         * @var ValidatorInterface
         */
        private $validator;
    
        /**
         * @InjectParams
         *
         * @param GradePointAverageRepository $gradePointAverageRepository
         * @param GradePointAveragePaginationFactoryInterface $gradePointAveragePaginationFactory
         * @param ValidatorInterface $validator
         */
        public function __construct(
            GradePointAverageRepository $gradePointAverageRepository,
            GradePointAveragePaginationFactoryInterface $gradePointAveragePaginationFactory,
            ValidatorInterface $validator
        )
        {
            $this->gradePointAverageRepository = $gradePointAverageRepository;
            $this->gradePointAveragePaginationFactory = $gradePointAveragePaginationFactory;
            $this->validator = $validator;
        }
    
        /**
         * @PreAuthorize("isAllowedToManageTheGradePointAverage(#gradePointAverage)")
         * @param GradePointAverage $gradePointAverage
         * @throws InvalidArgumentException
         */
        public function save(GradePointAverage $gradePointAverage)
        {
            $violationList = $this->validator->validate($gradePointAverage);
            if ($violationList->count()) {
                throw new InvalidArgumentException;
            }
    
            $this->gradePointAverageRepository->save($gradePointAverage);
        }
    }
    
    namespace MyApp\Controller;
    
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\HttpKernel\Log\LoggerInterface;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
    use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
    use Doctrine\Common\Collections\ArrayCollection;
    use FOS\RestBundle\View\View;
    use JMS\DiExtraBundle\Annotation\Service;
    use JMS\DiExtraBundle\Annotation\InjectParams;
    use JMS\SecurityExtraBundle\Annotation\PreAuthorize;
    use Knp\Component\Pager\Pagination\PaginationInterface;
    use MyApp\Manager\GradePointAverageManager;
    use MyApp\Model\GradePointAverage;
    use MyApp\Model\GradePointAverage\AssessmentValue;
    
    /**
     * @Service("grade_point_average_controller", parent="app.controller.abstract")
     * @Route("/gpa", service="grade_point_average_controller")
     */
    class GradePointAverageController extends BaseController
    {
        /**
         * @var GradePointAverageManager
         */
        private $gradePointAverageManager;
    
        private $logger;
    
        /**
         * @InjectParams
         *
         * @param GradePointAverageManager $gradePointAverageManager
         * @param LoggerInterface $logger
         */
        public function __construct(GradePointAverageManager $gradePointAverageManager, LoggerInterface $logger)
        {
            $this->gradePointAverageManager = $gradePointAverageManager;
            $this->logger = $logger;
        }
    
        // ...
    
        /**
         * @Route("/{id}", name="gpa.edit", requirements={"id" = "\d+"})
         * @Method("PUT")
         *
         * @param Request $request
         * @param GradePointAverage $gpa
         * @return View
         */
        public function editAction(Request $request, GradePointAverage $gpa)
        {
            $form = $this->formFactory->createNamed(null, 'gpa', $gpa, [
                'method' => 'PUT',
            ]);
            $form->handleRequest($request);
    
            foreach ($gpa->getAssessmentValues() as $av) {
                $this->logger->info('GPA ID PREVALIDATE IN CONTROLLER:'.$gpa->getId());
                $this->logger->info('PREVALIDATE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:'.$av->getAssessment()->getId());
                $this->logger->info('PREVALIDATE IN CONTROLLER ASSESSMENT VAL POINTS:'.$av->getValue());
            }
    
            /*
            // try reversing the order of the collection to see if that helps
            $assessmentVals = $gpa->getAssessmentValues()->toArray();
            $reversed = array_reverse($assessmentVals);
            $reversedColl = new ArrayCollection($reversed);
            $gpa->setAssessmentValues($reversedColl);
            */
    
            if ($form->isValid()) {
                foreach ($gpa->getAssessmentValues() as $av) {
                    $this->logger->info('GPA ID PRESAVE IN CONTROLLER:'.$gpa->getId());
                    $this->logger->info('PRESAVE IN CONTROLLER ASSESSMENT VAL ASSESSMENT ID:'.$av->getAssessment()->getId());
                    $this->logger->info('PRESAVE IN CONTROLLER ASSESSMENT VAL POINTS:'.$av->getValue());
                }
                $this->gradePointAverageManager->save($gpa);
    
                return new View($gpa, 204);
            }
    
            return new View($form);
        }
    
        // ...
    }
    
  • 共有1个答案

    柏夕
    2023-03-14

    尝试向表gpa_assessment_value添加一个Id,然后使用命令行生成实体。如果不添加id字段,从数据库映射orm文件将不会创建gpa_assessment_value.orm.yml。

    CREATE TABLE gpa_assessment_value
    (
        id                     bigserial       NOT NULL,
        grade_point_average_id bigint        NOT NULL,
        assessment_id          bigint        NOT NULL,
        point_value            numeric(4, 2) NOT NULL,
    
        PRIMARY KEY (id,assessment_id, grade_point_average_id),
        FOREIGN KEY (assessment_id) REFERENCES assessment,
        FOREIGN KEY (grade_point_average_id) REFERENCES grade_point_average
    );
    
     类似资料:
    • 问题内容: 如何使用复合主键作为外键?看来我的尝试无效。 问题答案: 该行: 是错的。您不能那样使用,这只是父表中PK约束的名称。要将复合主键用作外键,您必须向子表中添加相同数量(组成PK)的相同数据类型的列,然后在定义中使用这些列的组合:

    • 如何将复合主键用作外键?看来我的尝试没有成功。

    • 我在使用复合主键创建实体时遇到问题,该键也是外键。这是我的表和关系表原理图。当我想创建新闻实体时,我收到了带有null creatingnews的错误消息。新闻翻译有复合主键,外键引用到新闻表。 这是我的代码: 新闻聚合 新闻翻译 标签 新闻语言ID 在NewsFactory中,我希望使用NewsTranslation创建NewsAggregate,但有错误消息NullPointer。 新闻工厂

    • 问题内容: 我有以下表格如何将它们映射到JPA实体: 事件表与会议表具有一对多关系。如何在JPA中映射这种双向关系? 问题答案: 在JPA 2.1规范的第2.4.1节中讨论。

    • 我有一些实体: 当我试图保存新的cbonus记录时,出现异常: org.postgresql.util.PSQLException: ERROR: null值在列"bank_id"的关系"cBonus"违反了非空约束详细信息:失败的行包含(773, gp3, null, null, f)。 和查询 DEBUG 24817-[nio-8080-exec-4]org . hibernate . SQL

    • 问题内容: 我有一个类似的问题,如下所示,但解决方案无法解决我的问题。 休眠复合主键包含复合外键,如何映射 我正在尝试加入2个表,每个表都有一个带有部分外键引用的复合主键。 在一个: 在BPK中: 上面的方法给我这个异常: 你能帮忙吗? 问题答案: 假设f1和F2唯一标识A并存在于APK中,则可以通过几种方式使用JPA 2.0的派生ID。最容易显示的是: 这里的关键点是B对A的引用控制了外键字段f