The core principle of soft deletes is to preserve data integrity and historical records. Instead of deleting a record from the database, a flag is added to the record to indicate that it is no longer active. This flag is typically a boolean field, such as isDeleted
or deletedAt
(a timestamp indicating when the record was marked as deleted).
Specifications in Spring Data are based on the Specification pattern from Domain - Driven Design. A specification is a predicate that can be combined with other specifications to form complex queries. In Spring Data, specifications are used to build dynamic queries at runtime, allowing developers to filter data based on different criteria without writing multiple query methods.
The design philosophy behind soft deletes is centered around data preservation and auditability. By keeping deleted records in the database, organizations can maintain a complete history of all transactions. This is especially important in industries with strict regulatory requirements, such as finance and healthcare.
The design philosophy of specifications is to promote code reuse and flexibility. Instead of writing multiple query methods for different combinations of filters, developers can create reusable specifications and combine them as needed. This makes the codebase more maintainable and easier to understand.
ByStatusSpecification
can be used to filter records based on their status.and
, or
, and not
methods provided by Spring Data to combine multiple specifications to form complex queries.import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.time.LocalDateTime;
// Entity class with soft delete support
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private LocalDateTime deletedAt; // Timestamp for soft delete
// Getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDateTime getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(LocalDateTime deletedAt) {
this.deletedAt = deletedAt;
}
// Method to mark the product as deleted
public void delete() {
this.deletedAt = LocalDateTime.now();
}
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
// Repository interface with custom query to exclude deleted products
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query("SELECT p FROM Product p WHERE p.deletedAt IS NULL")
java.util.List<Product> findAllNonDeleted();
}
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
// Specification for filtering products by name
public class ProductNameSpecification implements Specification<Product> {
private final String name;
public ProductNameSpecification(String name) {
this.name = name;
}
@Override
public Predicate toPredicate(Root<Product> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
if (name == null) {
return criteriaBuilder.conjunction(); // Return a true predicate if name is null
}
return criteriaBuilder.like(root.get("name"), "%" + name + "%");
}
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
// Repository interface with specification support
public interface ProductRepository extends JpaRepository<Product, Long>, JpaSpecificationExecutor<Product> {
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
// Service class using specifications
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public List<Product> findProductsByName(String name) {
ProductNameSpecification spec = new ProductNameSpecification(name);
return productRepository.findAll(spec);
}
}
deletedAt
) to mark when a record was deleted. This provides more information and can be used for auditing and reporting.Soft deletes and specifications are powerful features in Spring Data that can significantly enhance the functionality and maintainability of Java applications. By understanding the core principles, design philosophies, performance considerations, and idiomatic patterns, developers can effectively implement these features in their projects. However, it is important to be aware of the common trade - offs and pitfalls and follow best practices to ensure a robust and scalable application.