This tutorial shows you how to use Hibernate's @SoftDelete annotation with examples.
A row that's inserted into a database can be deleted later for some reason. In many cases, we want to keep the deleted rows in the database by adding a column that indicates whether the row is deleted or not. That's also known as soft delete. If you use Hibernate version 6.4 or above, implementing soft delete should become easier since you can use the @SoftDelete
annotation. Below are the examples of how to use the annotation.
Using @SoftDelete
Annotation
The @SoftDelete
annotation can be placed on various levels which include package, type, field, method, and annotation type. It has some parameters but none of them are required. One of the parameters is strategy
which is used to set the strategy how to indicate that a value in a column means the row is deleted. It accepts an enum whose values are ACTIVE
and DELETED
. The default value if you don't pass the parameter is DELETED
. The differences of those two values can be seen on the table below.
Value | True | False |
---|---|---|
ACTIVE |
Row is active (non-deleted) | Row is inactive (deleted) |
DELETED |
Row is inactive (deleted) | Row is active (non-deleted) |
Another parameter that you need to know is columnName
which refers to the column that indicates whether the value is active or not. The default value depends on the strategy used. If the strategy is DELETED
, the default column name is deleted
. If the strategy is ACTIVE
, the default column name is active
. To define a different column to use, you can pass a columnName
value to the annotation.
If the value type stored in the database is not a boolean, you may also need to pass a custom AttributeConverter
as the converter
argument. The converter needs to handle conversions between the entity attribute type (boolean) and the database type.
Usage on Type/Class
For example, we have an entity type named Book
and we want to use soft deletion for it. To indicate that a row is deleted, there is a field named isRemoved
. If the value of isRemoved
is true, the row is deleted. Otherwise, the row is non-deleted. For that purpose, we can annotate the class with @SoftDelete
annotation. Since the column name is different from the default name (deleted
), we need to pass the columnName
to the annotation.
@SoftDelete(columnName = "isRemoved")
public class Book {
@Id
@UuidGenerator
private UUID id;
private String title;
private String author;
private boolean isRemoved;
}
We can test it by adding a code to remove a row.
public class BookService {
private final EntityManager entityManager;
public BookService(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Transactional
public void deleteBook(UUID id) {
Book book = this.entityManager.find(Book.class, id);
if (book == null) {
return;
}
this.entityManager.remove(book);
}
}
Below is the output when entityManager.remove
is invoked.
org.hibernate.SQL : update books set is_removed=true where id=? and is_removed=false
org.hibernate.orm.jdbc.bind : binding parameter (1:UUID) <- [877a4ef2-7e18-4a77-8e53-b2b6f78b525a]
As you can read, Hibernate will generate an update query instead of a delete query. For the case above, to perform a deletion, the generated query sets the is_removed
value to true. In addition, it automatically adds is_removed=false
in the criteria.
Not only for update queries, Hibernate also adds the is_removed=false
criteria for select queries.
public class BookService {
private final EntityManager entityManager;
public BookService(EntityManager entityManager) {
this.entityManager = entityManager;
}
public List<BookDto> fetchBooks() {
List<Book> books = this.entityManager.createQuery("SELECT b FROM Book b", Book.class)
.getResultList();
return books.stream()
.map(this::mapToBookDto)
.collect(Collectors.toList());
}
private BookDto mapToBookDto(Book book) {
return BookDto.builder()
.id(book.getId())
.title(book.getTitle())
.author(book.getAuthor())
.isDeleted(book.isRemoved())
.build();
}
// other methods
}
Below is the output when the select query above is executed.
org.hibernate.SQL : select b1_0.id,b1_0.author,b1_0.is_removed,b1_0.title from books b1_0 where b1_0.is_removed=false
The annotation makes it much simpler to have a soft delete feature since Hibernate automatically adds the criteria to filter out soft-deleted rows. That also reduces the chance of forgetting to add the criteria in some queries.
Usage on Field
The annotation can be defined on field or method level. It can be applied to the table of @ElementCollection
or @ManyToMany
.
For example, we have an element collection named tags
. If it's annotated with @SoftDelete
, the table must have a column that marks whether it's deleted or not.
public class Book {
@ElementCollection
@SoftDelete(columnName = "isDeleted")
private List<String> tags;
// other fields
}
Below is a query that fetches the collection.
List<Book> books = this.entityManager.createQuery("SELECT b FROM Book b JOIN FETCH b.tags", Book.class)
.getResultList();
From the output below, we can see that Hibernate adds the criteria to filter the rows from the element collection table.
org.hibernate.SQL : select b1_0.id,b1_0.author,b1_0.is_removed,t1_0.book_id,t1_0.tags,b1_0.title from books b1_0 left join book_tags t1_0 on b1_0.id=t1_0.book_id and t1_0.is_deleted=false where b1_0.is_removed=false
Usage on Package
If the annotation is defined on the package-level, it will have any effect on all entities in the package. This can be useful if you want to implement soft delete to all entities in a package. If you want to use a different soft delete config for an entity, you can define another annotation for the entity. Hibernate will use the more specific one.
@SoftDelete(columnName = "isRemoved")
package com.woolha.hibernate6.example.model;
Summary
Hibernate's @SoftDelete
annotation makes it easier for us to have entities that support soft delete. First, make sure the database table must have a column that indicates whether the row is deleted or not. You just need to add the annotation which can be put on package, type, field, method, or annotation type level. When Hibernate executes an operation to remove a row, it will generate an update query that sets the value of the column that's used for soft-deletion. In addition, the annotation also affects select, update, and delete queries for which Hibernate adds the criteria to filter out soft-deleted rows.
You can also read about: