A derived delete loads matching entities for removal; a JPQL delete issues a bulk statement and skips entity removal callbacks.
Spring Data JPA derived delete versus bulk delete: callbacks and memory cost
The names hide different work
Deleting expired ParcelHold rows by a derived delete method first selects the matching entities and removes them one by one. That path can run @PreRemove and JPA cascades. A JPQL delete sends one statement to the database; already managed ParcelHold objects stay stale and entity callbacks are bypassed. The SQL count can match while the application behavior differs.
Put side effects outside lifecycle assumptions
If removal must publish a durable event, write an outbox row in the same transaction through an explicit service, then delete under a policy whose behavior you test. An entity callback alone is a poor substitute for transaction-aware effects or an outbox. Bulk delete is a reasonable fit for maintenance data when the business contract does not depend on callbacks or entity-level cascades.
Bound the operation
A derived delete of 64 rows may be ordinary; one that loads millions of rows can exhaust heap before flush. Partition by a stable key and measure query count, memory and lock duration. A bulk delete still needs an indexed predicate and an explicit transaction. This example is an interface sketch, not a measured throughput result.
Implementation sketch
long deleteByTenantIdAndExpiredTrue(String tenantId);
@Modifying(clearAutomatically = true)
@Query("delete from ParcelHold h where h.tenantId = :tenantId " +
"and h.expired = true")
int deleteExpiredInBulk(String tenantId);Cost and verification
Derived deletion uses O(n) entity memory for n matches and may issue multiple deletes; bulk JPQL can use one statement but bypasses per-entity work. Database scan and lock costs still depend on the predicate and indexes.
Common Mistakes
- Do not expect @PreRemove to fire for a JPQL bulk delete.
- Do not infer low memory use from a derived method returning only a count.
- Do not switch deletion style without checking cascades, audit and outbox contracts.
Read next
Spring Data JPA bulk update: the managed entity can still hold the old value, Spring JPA entity lifecycle: managed changes and detached objects, Spring outbox transaction boundaries: where atomicity ends, Spring JPA fetch plans: measure N+1 queries before changing mappings.
