To delete data in Spring Boot with JPA and Hibernate, we may use the following ways
Use built-in Delete APIs of Spring Data JPA repositories
Use CascadeType.ALL or CascadeType.REMOVE attributes to delete the child entities when the parent entity is deleted. They may be used on @OneToOne, @OneToMany, @ManyToOne, and @ManyToMany
Use orphanRemoval attribute on @OneToMany or @OneToOne annotation to delete the child entities when they are removed from the relationship with the parent entity
Use Jakarta Persistence Query Language (JPQL) to delete a bulk of entities
Define status field in JPA and Hibernate entities to do a soft delete manually or with @Where and @SQLDelete
Using Delete APIs of Spring Data Repository
Spring Data Repository provides delete(entity)
and deleteById(entityId)
APIs to delete data with JPA and Hibernate entity
Suppose we define Customer JPA entity and Spring Data Repository as below
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
...
}
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
}
Then we can directly call the built-in Delete APIs of CustomerRepository to delete data
@Service
@Transactional
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
public void delete(Customer customer) {
customerRepository.delete(customer);
}
public void deleteById(Integer id) {
customerRepository.deleteById(id);
}
...
}
Using CascadeType.ALL or CascadeType.REMOVE
The above delete methods work fine as long as the JPA and Hibernate entity does not have any relationship mapping. Otherwise, we may gain an SQL exception in the console when CascadeType.ALL or CascadeType.REMOVE is not specified in the mapping annotations
Suppose we map a one-to-many relationship between Customer and Card
@Entity
public class Customer {
...
@OneToMany(mappedBy = "customer")
private Set<Card> cards;
...
}
@Entity
public class Card {
...
@ManyToOne
@JoinColumn
private Customer customer;
...
}
Then when customerRepository.delete(customer) or customerRepository.deleteById(customerId) is triggered, the following error is expected
Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails ...
To fix that, we may use CascadeType.ALL or CascadeType.REMOVE attribute on @OneToOne, @OneToMany, @ManyToOne, and @ManyToMany mapping annotations to delete associated entities when the parent entity is deleted
@Entity
public class Customer {
...
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
private Set<Card> cards;
...
}
Using orphanRemoval
Using orphanRemoval attribute on @OneToMany or @OneToOne annotation to delete the child entities when they are removed from the relationship with the parent entity
Let's set orphanRemoval = true
in the @OneToMany mapping between Customer and Card
@Entity
public class Customer {
...
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Card> cards;
...
}
@Entity
public class Card {
public static final String VISA = "VISA";
public static final String MASTER = "MASTER";
...
private String type;
@ManyToOne
@JoinColumn
private Customer customer;
...
}
Then when a card is removed from the associated collection, JPA and Hibernate would generate a corresponding DELETE SQL statement
customer.getCards().removeIf(c -> VISA.equals(c.getType()));
customerService.save(customer);
Using JPQL to delete a bulk of entities
When using CaseCadeType.ALL or CaseCadeType.REMOVE to delete an entity along with its associated entities, we may find the following in the console
Hibernate: delete from card where card_number=?
Hibernate: delete from card where card_number=?
Hibernate: delete from customer where id=?
That says JPA and Hibernate generates one by one SQL DELETE statement for each associated entities. It works but not optimal. Obviously, we only need 1 SQL DELETE statement to delete all associated entities. Fortunately, we may use JPQL to achieve that
JPQL is a database-independent query language with the syntax and usage like SQL. JPQL works on JPA and Hibernate entities, while SQL works on database tables
Let's define 2 custom JPQL delete queries in CustomerRepository and CardRepository
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
@Modifying
@Query("DELETE Customer c WHERE c.id = ?1")
void deleteByIdWithJPQL(int id);
}
public interface CardRepository extends JpaRepository<Card, Integer> {
@Modifying
@Query("DELETE Card c WHERE c.customer.id = ?1")
void deleteByCustomerId(int customerId);
}
Then call them to delete the associated entities along with the main entity
cardService.deleteByCustomerId(customer.getId());
customerService.deleteByIdWithJPQL(customer.getId());
The main entity should be deleted last, otherwise, a SQL exception would be thrown as we violated the constraints
Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails ...
Soft delete manually
In practice, data is crucial to every application and system, so in most cases, instead of deleting we may like to archive it for analyzing and auditing later
To achieve that, we may add a flag or status field into the JPA and Hibernate entity. We would also need to add additional fields to track the created and updated date-time
@Entity
public class Customer {
...
@OneToMany(mappedBy = "customer")
private Set<Card> cards;
private Boolean deleted;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
...
}
@Entity
public class Card {
...
@ManyToOne
@JoinColumn
private Customer customer;
private Boolean deleted;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
...
}
Then create a soft delete API to mark the flag field as deleted
@Service
@Transactional
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
public void softDelete(Integer id) {
Customer customer = customerRepository.findById(id).get();
customer.setDeleted(true);
for(Card card : customer.getCards()) {
card.setDeleted(true);
}
customerRepository.save(customer);
}
...
}
@CreationTimestamp and @UpdateTimestamp are Hibernate annotations, available since Hibernate version 4.3
We also need to custom the Get or Find API to exclude the deleted entities
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
...
List<Customer> findTop10ByDeletedFalse();
}
Soft delete with @Where and @SQLDelete
@Where and @SQLDelete are Hibernate annotations designed for soft deletion and defined on entities
We may use @Where(clause = ...) to exclude the entities with deleted status in all SELECT query and use @SQLDelete(sql = ...) to custom the default DELETE query generated by JPA and Hibernate
@SQLDelete(sql = "UPDATE Customer SET deleted = 1 WHERE id = ?")
@Where(clause = "deleted = 0")
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
...
private Boolean deleted;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}
@SQLDelete(sql = "UPDATE Card SET deleted = 1 WHERE card_number = ?")
@Where(clause = "deleted = 0")
@Entity
public class Card {
@Id
private Integer cardNumber;
...
private Boolean deleted;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}
When the Delete API of JPA and Hibernate are triggered
customerRepository.delete(customer);
customerRepository.deleteById(1);
Hibernate would generate the queries defined by @SqlDelete instead of the default DELETE queries
Hibernate: UPDATE Card SET deleted = 1 WHERE card_number = ?
Hibernate: UPDATE Customer SET deleted = 1 WHERE id = ?
When the Get or Find API of JPA and Hibernate are triggered
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
...
List<Customer> findTop10By();
@EntityGraph(attributePaths = "cards", type = EntityGraph.EntityGraphType.LOAD)
List<Customer> findAllByIdIsIn(List<Integer> ids);
}
@Service
@Transactional
public class CustomerService {
...
public List<Customer> findTop10() {
List<Customer> customerIds = customerRepository.findTop10By();
return customerRepository.findAllByIdIsIn(
customerIds.stream().map(Customer::getId).collect(Collectors.toList())
);
}
}
Hibernate would append the queries defined by the @Where annotation to include the entities with the undeleted status
Hibernate: select customer0_.id as id1_1, ...
from customer customer0_
where ( customer0_.deleted = 0)
limit ?
Hibernate: select customer0_.id as id1_1_0_, ...
from customer customer0_
left outer join card cards1_ on
customer0_.id=cards1_.customer_id
and ( cards1_.deleted = 0)
where ( customer0_.deleted = 0)
and (customer0_.id in (? , ?))
Conclusion
In this tutorial, we learned various ways to delete data in JPA and Hibernate by using Spring Data Repository Delete APIs, CascadeType, orphanRemoval, JPQL, and soft delete. You may find the source code on GitHub