Skip to content

Batch imported relationship cascade deletes - #3895

Open
mvanhorn wants to merge 1 commit into
ebean-orm:masterfrom
mvanhorn:fix/1750-delete-cascade-n-plus-one
Open

mvanhorn wants to merge 1 commit into
ebean-orm:masterfrom
mvanhorn:fix/1750-delete-cascade-n-plus-one

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

A cascaded permanent delete of a parent whose children own relationships to other beans issues a separate delete for every child and every related bean. Cascades under those related beans run once per bean, and the statement count grows with the size of the graph.

Delete-by-id for a bean with owning-side cascade relationships loads the rows and then calls deleteCascade on that list, walking one bean at a time. Imported targets are followed per bean, so two associations to the same type are never merged into one id set and each target loads and deletes its own children again. Nothing in that walk collects the ids for a bulk delete.

What this changes

For a hard delete, when the descriptor and every cascaded bean can be removed in bulk:

  • The fetched beans' non-null imported target ids are grouped by type in first-seen order, and relationships that share a type share one delete.
  • Children and these owners are deleted via deleteChildrenAndBeans, which does not fetch imported relationships again.
  • The transaction is flushed so owner deletes hit the database before the referenced beans are removed. Then each imported type is deleted from its collected ids.
  • An empty fetch returns 0.

These stay on the per-bean cascade: multi-tenant descriptors, soft delete, descriptors that are not delete-by-bulk, and a descriptor still on the stack (a cycle). Many-to-many and element collections are not walked by the eligibility check, and a descriptor already accepted is not walked again.

How it was tested

mvn -B -ntp -pl ebean-test -am test -Dtest=TestPersistCascade -Dsurefire.failIfNoSpecifiedTests=false passed 7 tests locally and in a Linux container (maven:3-eclipse-temurin-21). The tests in ebean-test/src/test/java/org/tests/iud/TestPersistCascade.java fail without the fix (checked by reverting the source changes). The ebean-querybean tests also pass locally on this branch.

Fixes #1750

For a hard delete, when the descriptor and every cascaded bean can be
removed in bulk, the fetched beans' non-null imported target ids are
grouped by type in first-seen order and relationships that share a type
share one delete. Children and these owners are deleted via
deleteChildrenAndBeans, which does not fetch imported relationships
again; the transaction is flushed so owner deletes hit the database
before the referenced beans are removed; then each imported type is
deleted from its collected ids. An empty fetch returns 0. Multi-tenant
descriptors, soft delete, descriptors that are not delete-by-bulk, and a
descriptor still on the stack (a cycle) stay on the per-bean cascade;
many-to-many and element collections are not walked by the eligibility
check, and a descriptor already accepted is not walked again.

Fixes ebean-orm#1750

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Delete cascade N+1 problem

1 participant