实体插入触发Doctrine唯一约束异常问题求助
看起来你的问题出在两个关键地方:查询逻辑错误导致无法正确检测已存在的实体,以及可能存在的主键生成逻辑问题,另外还有SQL注入的风险需要修复。
首先看报错信息:插入时出现主键重复,说明当你认为实体不存在要插入时,这个ID已经在survey_database_answers表中存在了——根本原因是你的查询没找到已存在的记录,但实际上它是存在的,或者你错误地给新实体分配了一个已被使用的ID。
1. 修正查询逻辑(核心问题)
你的原SQL查询关联表的方式有明显问题:
select sa.id from survey_answers as sa join survey_database_answers as sda on sa.id = sda.id -- 这里的关联条件大概率错误! join answers as a on a.id = sda.id_answer where id_client= ".$client->getId()." and id_question = ".$question->getId()." and id_survey = ".$survey->getId();
通常survey_database_answers作为关联表,它的主键id应该是独立自增的,而外键应该是类似id_survey_answer来关联survey_answers的id,而不是让sda.id直接等于sa.id。这个错误导致你查询不到已存在的sda记录,进而触发插入逻辑,但此时你可能无意中给新实体设置了一个已存在的ID。
修正后的查询应该直接从sda表出发,正确关联外键(请根据你的实际表结构替换id_survey_answer为真实外键字段名):
select sda.id from survey_database_answers as sda join survey_answers as sa on sda.id_survey_answer = sa.id join answers as a on a.id = sda.id_answer where sa.id_client = ? and sa.id_question = ? and sa.id_survey = ?
2. 修复SQL注入风险
绝对不要直接把变量拼接到SQL语句里,这会导致严重的SQL注入漏洞。改用参数绑定的方式执行SQL:
$result = $storage->executeSQL($query, [ $client->getId(), $question->getId(), $survey->getId() ]);
3. 优化实体操作逻辑
原代码中,当找到已存在的记录时,你新建了一个实体然后设置ID,这不如直接从EntityManager中获取托管实体更稳妥。另外,插入新实体时,确保你的SurveyDatabaseAnswer实体的id字段是自动生成的(比如在Doctrine映射中添加@GeneratedValue(strategy="AUTO")),不要手动设置ID,避免重复。
修改后的完整函数
private function updateDatabaseAnswer($client, $survey, $answer, $question){ $storage = $this->di->find('storage'); // 修正后的查询,使用正确的关联条件和参数绑定 $query = "select sda.id from survey_database_answers as sda join survey_answers as sa on sda.id_survey_answer = sa.id join answers as a on a.id = sda.id_answer where sa.id_client = ? and sa.id_question = ? and sa.id_survey = ?"; $result = $storage->executeSQL($query, [ $client->getId(), $question->getId(), $survey->getId() ]); if(!empty($result)){ // 从数据库获取已存在的托管实体 $updated_answer = $storage->find(SurveyDatabaseAnswer::class, $result[0]); // 更新需要修改的字段 $updated_answer->setAnswer($answer); $storage->merge($updated_answer); } else { // 新建实体,依赖自动生成的主键 $updated_answer = new SurveyDatabaseAnswer(); $updated_answer->setClient($client); $updated_answer->setSurvey($survey); $updated_answer->setAnswer($answer); $storage->persist($updated_answer); } $storage->flush(); }
额外检查点
- 确认
SurveyDatabaseAnswer的实体映射中,id字段配置了自动生成策略,比如:/** * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") * @ORM\Column(type="integer") */ private $id; - 验证你的数据库表结构,确保
survey_database_answers的主键是自增的,且外键关联正确。
内容的提问来源于stack exchange,提问作者afdi5

