Doctrine持久化时未设置外键值触发非空约束异常
Doctrine持久化时user_id为空导致非空约束违反问题
错误信息:
An exception occurred while executing 'INSERT INTO user_access_company (end_at, created_at, updated_at, user_id, company_id) VALUES (?, ?, ?, ?, ?)' with params [null, "2022-12-12 23:06:57+0000", "2022-12-12 23:06:57+0000", null, 82]: SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column "user_id" violates not-null constraint
实体类代码
class Participant implements ParticipantInterface { protected $id; protected $source; protected $identifier; protected $firstName; protected $lastName; protected $emailAddress; protected $createdAt; protected $updatedAt; } class UserAccessCompany { private Participant $user; private Company $company; private ?\DateTimeImmutable $endAt; private \DateTimeImmutable $createdAt; private \DateTimeImmutable $updatedAt; }
XML映射文件
Participant映射
<?xml version="1.0" encoding="UTF-8"?> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd" xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping" > <entity name="Participant" table="message_participant"> <id name="id" type="bigint"> <generator strategy="AUTO" /> </id> <field name="source" type="string" /> <field name="identifier" type="integer" /> <field name="firstName" type="string" /> <field name="lastName" type="string" nullable="true"/> <field name="emailAddress" type="string" nullable="true"> <field name="createdAt" type="datetime"><gedmo:timestampable on="create"/></field> <field name="updatedAt" type="datetime"><gedmo:timestampable on="update"/></field> </entity> </doctrine-mapping>
UserAccessCompany映射
<?xml version="1.0" encoding="utf-8"?> <doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd"> <entity name="UserAccessCompany" table="user_access_company"> <id name="user" association-key="true"/> <id name="company" association-key="true"/> <field name="endAt" type="datetimetz_immutable" column="end_at" nullable="true"/> <field name="createdAt" type="datetimetz_immutable" column="created_at"><gedmo:timestampable on="create"/></field> <field name="updatedAt" type="datetimetz_immutable" column="updated_at"><gedmo:timestampable on="create"/></field> <many-to-one field="user" target-entity="Participant"> <join-columns> <join-column name="user_id" referenced-column-name="identifier"/> </join-columns> </many-to-one> <many-to-one field="company" target-entity="Company"> <join-columns> <join-column name="company_id"/> </join-columns> </many-to-one> </entity> </doctrine-mapping>
插入数据代码
$userAccessCompany = new UserAccessCompany($user, $company, $endAt); $this->getEntityManager()->persist($userAccessCompany); $this->getEntityManager()->flush();
补充说明
- $user和$company均为已持久化到数据库的实体,打印
$user->identifier能看到有效值,但生成的SQL语句中user_id为null。 - Company实体的配置方式类似(未指定inversedBy/mappedBy),生成迁移时Doctrine已创建userAccessCompany.user_id关联participant.identifier的外键。
- 所有实体均已定义getter和setter,尝试过注解映射、给UserAccessCompany添加独立id并移除联合主键,但问题依旧。
- 当前使用Symfony 4.4和doctrine/orm 2.7.3版本。
问题原因及解决方案
核心原因
Doctrine默认依赖实体的主键字段处理关联关系,但你在UserAccessCompany的user关联中指定了referenced-column-name="identifier",而Participant的主键是id字段,并非identifier。Doctrine无法正确识别非主键字段作为关联字段的持久化规则,导致插入时无法获取identifier的值填充到user_id。
解决步骤
调整Participant的主键配置(如果identifier是业务主键)
如果identifier是Participant的业务唯一主键,直接将其设为实体主键:<entity name="Participant" table="message_participant"> <!-- 替换原id配置,将identifier设为主键 --> <id name="identifier" type="integer"> <generator strategy="NONE" /> <!-- 根据实际生成策略调整,自增用AUTO --> </id> <!-- 原id字段改为普通字段(若需保留) --> <field name="id" type="bigint" nullable="true"/> <!-- 其他字段保持不变 --> <field name="source" type="string" /> <field name="firstName" type="string" /> <field name="lastName" type="string" nullable="true"/> <field name="emailAddress" type="string" nullable="true"/> <field name="createdAt" type="datetime"><gedmo:timestampable on="create"/></field> <field name="updatedAt" type="datetime"><gedmo:timestampable on="update"/></field> </entity>给identifier添加唯一约束(若无需改主键)
若identifier只是业务唯一键而非主键,给它添加唯一约束,确保Doctrine能正确追踪该字段:<!-- 在Participant映射中修改identifier字段 --> <field name="identifier" type="integer" unique="true" />检查构造函数赋值逻辑
确保UserAccessCompany的构造函数正确初始化$user属性:class UserAccessCompany { public function __construct(Participant $user, Company $company, ?\DateTimeImmutable $endAt = null) { $this->user = $user; $this->company = $company; $this->endAt = $endAt; // 手动初始化时间字段(若gedmo未自动处理) $this->createdAt = new \DateTimeImmutable(); $this->updatedAt = new \DateTimeImmutable(); } }强制刷新实体状态
在持久化前刷新$user实体,确保Doctrine持有最新的字段值:$this->getEntityManager()->refresh($user); $userAccessCompany = new UserAccessCompany($user, $company, $endAt); $this->getEntityManager()->persist($userAccessCompany); $this->getEntityManager()->flush();
内容的提问来源于stack exchange,提问作者ᴄʀᴏᴢᴇᴛ
相关产品推荐
相关产品推荐

