EntityManager.merge copies detached state, so a sparse API payload must not masquerade as a complete entity snapshot.
JPA detached merge: prevent partial requests from erasing stored fields
A missing field becomes a write
A client sends only a receipt status and ID. If the controller constructs a detached ReceiptEntity with the other fields left null, then calls repository save, JPA may merge the nulls into a managed copy. That makes a patch request behave like a destructive replacement. New-state detection decides persist versus merge; it does not make a partial entity safe.
Load then change allowed fields
Keep a request DTO separate from the entity. In one tenant-scoped transaction, query the row using both tenant and receipt ID, verify the expected revision, and update only fields the command permits. Dirty checking writes the changed managed entity at flush. Put invariant checks before mutation. The attached entity stays owned by this persistence context; do not hand it to a background thread or serialize it directly into an API response.
Make conflicts visible
Test a sparse request with an existing non-null customer reference and total. The update should preserve both. Test a stale revision and a cross-tenant ID; neither may change the row. A narrow JPQL update can be better for a single column if it includes the tenant and version predicates, but bulk updates bypass managed-entity synchronization.
Implementation contract
@Transactional
public ReceiptView changeStatus(UUID tenantId, UUID receiptId, long expectedRevision, String nextStatus) {
ReceiptEntity receipt = repository.findByTenantIdAndReceiptId(tenantId, receiptId).orElseThrow();
if (receipt.getRevision() != expectedRevision) throw new OptimisticLockingFailureException("stale receipt");
receipt.changeStatus(nextStatus);
return ReceiptView.from(receipt);
}Cost and verification
Load-and-change usually needs a SELECT plus an UPDATE. It preserves field-level intent and authorization; a predicate update can use one SQL statement when its lost-update contract is explicit.
Common Mistakes
- Do not bind a partial JSON object directly onto a managed entity.
- Do not call merge on a sparse entity and expect null fields to be ignored.
- Do not check ownership outside the transaction that performs the change.
Read next
Spring Data JPA assigned IDs: tell save when an entity is new, Spring Data JPA bulk update: the managed entity can still hold the old value, Spring tenant command transaction: keep state, event and replay record together, Spring JDBC versioned tenant update: inspect the affected row count, Spring JPA entity lifecycle: managed changes and detached objects.
