Implementing Soft Delete Logic

1. Designing Soft Delete Schema

ColumnDetail
deleted_atTIMESTAMPTZ NULL; NULL = active
deleted_byFK to user
delete_reasonOptional audit text
IndexPartial: WHERE deleted_at IS NULL

2. Implementing Soft Delete Filter

Example: Hibernate @SQLRestriction

@Entity
@SQLDelete(sql = "UPDATE orders SET deleted_at = now() WHERE id = ?")
@SQLRestriction("deleted_at IS NULL")
public class Order { ... }

3. Implementing Restore Logic

Example: Restore endpoint

@PostMapping("/orders/{id}/restore")
@PreAuthorize("hasRole('ADMIN')")
public Order restore(@PathVariable UUID id) {
    int updated = jdbc.update(
        "UPDATE orders SET deleted_at = NULL WHERE id = ? AND deleted_at IS NOT NULL", id);
    if (updated == 0) throw new NotFoundException("order", id);
    return orderRepo.findById(id).orElseThrow();
}

4. Handling Cascade Soft Deletes

ApproachDetail
Application-levelService traverses graph, marks each
TriggerDB cascades soft-delete
Event-drivenPublish event; listeners cascade
Trade-offTriggers can hide behavior

5. Implementing Hard Delete Logic

TriggerDetail
Retention expirye.g., delete soft-deleted after 30 days
Compliance requestGDPR right-to-erasure
Admin actionTwo-step confirmed
CascadeFK ON DELETE handling

6. Implementing Soft Delete Audit

FieldDetail
actorUser who deleted
timestampUTC
reasonCompliance / cleanup / user request
restore eventsTrack restores too

7. Handling Unique Constraints with Soft Delete

ApproachDetail
Partial unique indexUNIQUE (email) WHERE deleted_at IS NULL
Append timestampOn delete, suffix value to free key
Composite keyInclude deleted_at in unique

8. Implementing Soft Delete Queries

UseQuery
Default readsWHERE deleted_at IS NULL
Trash viewWHERE deleted_at IS NOT NULL
AllNo filter (admin only)
BypassNative query when needed

9. Implementing Permanent Delete After Period

Example: Cleanup job

@Scheduled(cron = "0 0 3 * * *")
public void purgeOldDeletes() {
    jdbc.update("""
        DELETE FROM orders
        WHERE deleted_at IS NOT NULL
          AND deleted_at < now() - interval '30 days'""");
}

10. Handling Soft Delete in Joins

ConcernDetail
Apply per tableFilter each joined table
ViewDefine view that hides deleted
WatchOUTER joins can leak deleted rows as NULLs

11. Implementing Soft Delete Indicators

UI ElementDetail
Badge "Deleted"In admin trash view
StrikethroughVisual cue
Restore buttonFor authorized users
Days-until-purgeSet expectations

12. Implementing Bulk Soft Delete

Example: Bulk update

UPDATE orders SET deleted_at = now(), deleted_by = :actor
WHERE id = ANY(:ids) AND deleted_at IS NULL;