You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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会优先从缓存中获取对象,而非查询数据库,导致返回已被删除的子实体。

解决方法

  1. 清除UnitOfWork缓存
    在删除操作flush()后,调用$entityManager->clear()清除所有缓存,之后再查询就能从数据库获取最新状态。但此操作会清除所有实体的缓存,可能影响后续其他操作。

    $entityManager->remove($s1);
    $entityManager->flush();
    $entityManager->clear(); // 清除缓存
    
    // 重新查询
    $shelfAfterDel = $entityManager->find(Shelf::class, $shelfId);
    $bookAfterDel = $entityManager->find(Book::class, $bookId);
    
  2. 显式移除子实体
    在删除父对象前,手动遍历并移除关联的子实体,再执行flush()。这样ORM会跟踪到子实体的删除操作,同步更新缓存。

    foreach ($s1->getBooks() as $book) {
        $entityManager->remove($book);
    }
    $entityManager->remove($s1);
    $entityManager->flush();
    
  3. 配置ORM级联删除
    在OneToMany关联注解中添加cascade: ['remove'],让ORM在删除父对象时自动处理子实体的删除,无需依赖数据库级联。同时可以保留onDelete: 'CASCADE'作为数据库层面的兜底,防止ORM操作失败时出现数据不一致。
    修改Shelf类中的关联注解:

    #[ORM\OneToMany(targetEntity: Book::class, mappedBy: 'shelf', cascade: ['remove'])]
    private Collection $books;
    

内容的提问来源于stack exchange,提问作者DocDbg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:49:55