Doctrine使用onDelete: 'cascade'删除后仍返回已删除子Entity问题求助
Doctrine级联删除后ORM缓存导致子实体查询异常问题
使用Doctrine的onDelete: 'cascade'配置时,出现以下异常情况:删除父对象后,数据库已成功级联删除子对象,但调用entityManager->find()再次查询时,父实体返回null符合预期,子实体却仍能被ORM查询到,而实际数据库中已不存在该子对象。
代码示例
主脚本代码
<?php require_once "bootstrap.php"; // 创建对象 $s1 = new Shelf(); $s1->setCategory("Science fiction"); $entityManager->persist($s1); $b1 = new Book(); $b1->setAuthor("Isaac Asimov"); $b1->setTitle("End of Eternity"); $b1->setShelf($s1); $entityManager->persist($b1); $entityManager->flush(); $shelfId = $s1->getId(); $bookId = $b1->getId(); // 删除对象 $entityManager->remove($s1); $entityManager->flush(); // --> 数据库中已删除书架和书籍 // 重新查询对象 $shelfAfterDel = $entityManager->find(Shelf::class, $shelfId); $bookAfterDel = $entityManager->find(Book::class, $bookId); echo "shelf: " . ($shelfAfterDel!=null? $shelfAfterDel->getCategory() : "null") . "\n"; // --> 输出 'null' (符合预期) echo "book: " . ($bookAfterDel!=null? $bookAfterDel->getTitle() : "null") . "\n"; // --> 输出 'End of Eternity' (不符合预期)
Shelf.php
<?php use Doctrine\ORM\Mapping as ORM; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; #[ORM\Entity] #[ORM\Table(name: 'shelf')] class Shelf { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; #[ORM\Column] private string $category; #[ORM\OneToMany(targetEntity: Book::class, mappedBy: 'shelf')] private Collection $books; public function __construct() { $this->books = new ArrayCollection(); } public function getId(): ?int { return $this->id; } public function getCategory(): string { return $this->category; } public function setCategory(string $category): void { $this->category = $category; } public function addBook(Book $book): void { $this->books[] = $book; } }
Book.php
<?php use Doctrine\ORM\Mapping as ORM; #[ORM\Entity] #[ORM\Table(name: 'book')] class Book { #[ORM\Id] #[ORM\Column] #[ORM\GeneratedValue] private ?int $id = null; #[ORM\Column] private string $author; #[ORM\Column] private string $title; #[ORM\ManyToOne(targetEntity: Shelf::class, inversedBy: 'books')] #[ORM\JoinColumn(onDelete: 'CASCADE', nullable: false)] private Shelf $shelf; public function setShelf(Shelf $shelf): void { $this->shelf = $shelf; } public function getShelf(): Shelf { return $this->shelf; } public function getId(): ?int { return $this->id; } public function getAuthor(): string { return $this->author; } public function setAuthor(string $author): void { $this->author = $author; } public function setTitle($title): void { $this->title = $title; } public function getTitle():string { return $this->title; } }
bootstrap.php
require_once "vendor/autoload.php"; $config = ORMSetup::createAttributeMetadataConfiguration( paths: [__DIR__ . '/src'], isDevMode: true, ); $connection = DriverManager::getConnection([ 'driver' => 'pdo_mysql', 'host' => '127.0.0.1', 'port' => '3306', 'user' => 'root', 'password' => 'XXXXXX', 'dbname' => 'phpunit', ], $config); $entityManager = new EntityManager($connection, $config);
composer.json
{ "require": { "php": ">=8.0.0", "monolog/monolog": "^3.8", "doctrine/orm": "^3", "doctrine/dbal": "^4", "symfony/cache": "^7" }, "autoload": { "psr-4": { "": "src/" } }, "archive": { "exclude": [ "vendor", ".DS_Store", "*.log" ] } }
问题原因与解决思路
核心原因
Doctrine的EntityManager维护着一级缓存(UnitOfWork),删除父对象时,数据库级联删除子实体的操作是在数据库层面完成的,ORM并未感知到子实体已被删除,因此子实体仍然存在于缓存中。调用find()方法时,ORM会优先从缓存中获取对象,而非查询数据库,导致返回已被删除的子实体。
解决方法
清除UnitOfWork缓存
在删除操作flush()后,调用$entityManager->clear()清除所有缓存,之后再查询就能从数据库获取最新状态。但此操作会清除所有实体的缓存,可能影响后续其他操作。$entityManager->remove($s1); $entityManager->flush(); $entityManager->clear(); // 清除缓存 // 重新查询 $shelfAfterDel = $entityManager->find(Shelf::class, $shelfId); $bookAfterDel = $entityManager->find(Book::class, $bookId);显式移除子实体
在删除父对象前,手动遍历并移除关联的子实体,再执行flush()。这样ORM会跟踪到子实体的删除操作,同步更新缓存。foreach ($s1->getBooks() as $book) { $entityManager->remove($book); } $entityManager->remove($s1); $entityManager->flush();配置ORM级联删除
在OneToMany关联注解中添加cascade: ['remove'],让ORM在删除父对象时自动处理子实体的删除,无需依赖数据库级联。同时可以保留onDelete: 'CASCADE'作为数据库层面的兜底,防止ORM操作失败时出现数据不一致。
修改Shelf类中的关联注解:#[ORM\OneToMany(targetEntity: Book::class, mappedBy: 'shelf', cascade: ['remove'])] private Collection $books;
内容的提问来源于stack exchange,提问作者DocDbg
相关产品推荐
相关产品推荐

