Spring Boot cookbook · Intermediate
Implement soft delete with Hibernate
Let users 'delete' records that can be restored and excluded from normal queries.
Approach
Hibernate 6.4+ supports @SoftDelete, which turns DELETE into an update of a boolean column and filters deleted rows automatically. For a timestamp column and restore support, combine @SQLDelete with @SQLRestriction (Hibernate 6.3+). Add a partial index or include the flag in indexes, and plan for unique constraints on non-deleted rows.
Solution
@Entity
@Table(name = "projects")
@SQLDelete(sql = "UPDATE projects SET deleted_at = now() WHERE id = ? AND version = ?")
@SQLRestriction("deleted_at IS NULL")
public class Project {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id;
private String name;
private Instant deletedAt;
@Version private long version;
}
public interface ProjectRepository extends JpaRepository<Project, Long> {
// Native query bypasses @SQLRestriction to find deleted rows for restore
@Modifying
@Query(value = "UPDATE projects SET deleted_at = NULL WHERE id = :id", nativeQuery = true)
int restore(long id);
}
// Unique name among non-deleted projects (PostgreSQL):
// CREATE UNIQUE INDEX ux_projects_name_active ON projects (tenant_id, name) WHERE deleted_at IS NULL;
Pitfalls
- Unique constraints that block re-creating a 'deleted' name.
- Soft-deleted personal data still subject to deletion requests (GDPR) — schedule hard deletes.
- Joins from other entities still loading deleted rows in some mappings.
Variations
@SoftDelete(strategy = SoftDeleteType.TIMESTAMP)in newer Hibernate versions.- Archive tables instead of flags for large datasets.
Further reading: Hibernate: Soft delete
Reference: https://docs.jboss.org/hibernate/orm/6.6/userguide/html_single/Hibernate_User_Guide.html#soft-delete
More Spring Boot recipes
- Add correlation IDs to Spring Boot logs
- Add retries and a circuit breaker with Resilience4j
- Authenticate machine clients with API keys in Spring Security
- Cache service results in Redis with @Cacheable
- Consume Kafka messages with retries and a dead-letter topic
- Handle concurrent updates with optimistic locking
- Make POST endpoints idempotent in Spring Boot
- Map entities to DTOs with MapStruct