Hibernate Soft Delete: @SoftDelete Example

In most applications, there is always a requirement to soft delete some part of data, rather than purging from the system completely. This data is generally needed for reporting and analytics purposes. For example, in an Issue management system, we can archive some issues so they can appear in reports, but they are gone from the active views of the customer.

Traditionally, hibernate supported the soft deleting the records using the @SQLDelete annotation. But this approach looked more like a hack of a feature that must be directly supported.

Since version 6.4, Hibernate @SoftDelete annotation provides first-class support for the soft delete feature. It transparently manages the indicator column where an indicator value is stored marking the record soft deleted or non-deleted.

In this tutorial, we will discuss the Hibernate @SoftDelete annotation with examples.

Once a record has been soft deleted using @SoftDelete, it will be excluded from all query operations, except we explicitely query for the deleted records using native SQL queries.

1. @SoftDelete an Entity

The @SoftDelete annotation marks an entity or a collection for soft-deletion rather than permanently removing them from the database. Internally, it handles the “deletions” from a database table by setting a column in the table to indicate deletion.

For example, in the following code, we are marking the EmployeeEntity for soft deletion.

@Entity
@SoftDelete
public class EmployeeEntity implements Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE)
  @Column(name = "ID") private Integer employeeId;

  @NaturalId
  @Column private String email;
  @Column private String firstName;
  @Column private String lastName;

  // setters and getters
}

By default, when the database is initialized with the Employee table, it has an additional column ‘deleted’ which can store the values ‘0’ and ‘1’.

  • ‘0’: row is active or not deleted.
  • ‘1’: row is inactive or deleted.

When we delete an active row, hibernate internally updates the value of ‘deleted‘ column to 1.

EmployeeEntity emp = new EmployeeEntity("Lokesh", "Gupta", "admin@howtodoinjava.com");

EntityManager entityManager = emf.createEntityManager();

entityManager.getTransaction().begin();
entityManager.persist(emp);		//Create new employee
entityManager.getTransaction().commit();

entityManager.getTransaction().begin();
entityManager.remove(emp);		//Delete the employee
entityManager.getTransaction().commit();

Notice the logs in the console. The ‘INSERT’ statement has an extra column ‘deleted’ with the value set to ‘0’. Later when deleting the employee, the value of the ‘deleted‘ column is updated to ‘1’.

Hibernate: select next_val as id_val from Employee_SEQ for update
Hibernate: update Employee_SEQ set next_val= ? where next_val=?
Hibernate: insert into Employee (email,firstName,lastName,deleted,ID) values (?,?,?,0,?)
Hibernate: update Employee set deleted=1 where ID=? and deleted=0

2. Customizing the Indication Column Name and Values used for Soft Deletion

Apart from the above-described default behavior (column name ‘deleted’ and values ‘0 / 1’), hibernate allows us to customize the column names and stored values according to our preferences or project needs.

2.1. Column Name

There are three ways to define the column name for managing the soft deletion indication values. Lets understand them.

AnnotationDescription
@SoftDelete
@SoftDelete(strategy=DELETED)
The default naming strategy that creates the column with name ‘deleted‘.
@SoftDelete(strategy=ACTIVE)Creates the column with name ‘active‘.
@SoftDelete(columnName = “archived”)The custom naming strategy that creates the column with name ‘archived‘.

In the following example, we are using the ‘strategy = ACTIVE‘ on EmployeeEntity.

@Entity
@SoftDelete(strategy = SoftDeleteType.ACTIVE)
public class EmployeeEntity implements Serializable {
	//...
}

Run the previous test code again and observe the output. It creates the indication column with name ‘active‘ and uses the values 0 and 1.

Hibernate: select next_val as id_val from Employee_SEQ for update
Hibernate: update Employee_SEQ set next_val= ? where next_val=?
Hibernate: insert into Employee (email,firstName,lastName,active,ID) values (?,?,?,1,?)
Hibernate: update Employee set active=0 where ID=? and active=1

2.2. Indication Values

The indication values refer to the actual values stored in the database to indicate a soft deletion of a row. By default, hibernate will store the values as 0 and 1. We can further customize it in the following ways:

AnnotationDescription
@SoftDelete
@SoftDelete(converter = NumericBooleanConverter.class)
It is the default. Stores the values as ‘0’ for false and ‘1’ for true.
@SoftDelete(converter = TrueFalseConverter.class)Stores the values as ‘F’ for false and ‘T’ for true.
@SoftDelete(converter = YesNoConverter.class)Stores the values as ‘N’ for false and ‘Y’ for true.

In the following example, we are using the ‘YesNoConverter‘ converter for indication values in EmployeeEntity.

@Entity
@SoftDelete(converter = YesNoConverter.class)
public class EmployeeEntity implements Serializable {
	//...
}

Run the previous test code again and observe the output. It creates the default column name ‘deleted‘, and used the values ‘Y’ and ‘N’.

Hibernate: select next_val as id_val from Employee_SEQ for update
Hibernate: update Employee_SEQ set next_val= ? where next_val=?
Hibernate: insert into Employee (email,firstName,lastName,deleted,ID) values (?,?,?,'N',?)
Hibernate: update Employee set deleted='Y' where ID=? and deleted='N'

3. Soft Delete an Collection

Apart from using on @Entity, we can apply @SoftDelete on the following collection types:

  • @ElementCollection
  • @ManyToMany

In the case of @OneToMany and @ManyToMany, the mapped entity may itself be soft deletable which is handled transparently.

Annotating a @OneToMany association with @SoftDelete will throw an exception. In the case, the mapped entity may itself be soft deletable which is handled transparently.

In the following example, we are adding an element collection that stores the phone numbers of an employee.

@Entity
@SoftDelete
public class EmployeeEntity implements Serializable {

  // other fields

  @ElementCollection
  @CollectionTable(name = "PhoneNumbers", joinColumns = @JoinColumn(name = "employeeId"))
  @Column(name = "PhoneNumber")
  @SoftDelete
  private Collection<String> phoneNumbers = new ArrayList<>();

  // getters and setters
}

To test the soft delete feature, initially we add 3 phone numbers to an employee. Later we remove a phone number from the collection.

EmployeeEntity emp = new EmployeeEntity("Amit", "Gupta", "amit@email.com");
emp.setPhoneNumbers(List.of("111111", "222222", "333333"));

EntityManager entityManager = emf.createEntityManager();

entityManager.getTransaction().begin();
entityManager.persist(emp);
entityManager.getTransaction().commit();

entityManager.getTransaction().begin();
emp.getPhoneNumbers().remove("111111");
entityManager.getTransaction().commit();

Observe the generated SQL queries. When we delete a phone number, Hibernate first soft deletes all 3 phone numbers and then immediately adds the two active phone numbers, thus effectively soft deleting the third phone number from the database table.

Hibernate: select next_val as id_val from Employee_SEQ for update
Hibernate: update Employee_SEQ set next_val= ? where next_val=?
Hibernate: insert into Employee (email,firstName,lastName,deleted,ID) values (?,?,?,0,?)
Hibernate: insert into Emails (employeeId,Email,deleted) values (?,?,0)
Hibernate: insert into Emails (employeeId,Email,deleted) values (?,?,0)
Hibernate: insert into Emails (employeeId,Email,deleted) values (?,?,0)
Hibernate: update Emails set deleted=1 where employeeId=? and deleted=0
Hibernate: insert into Emails (employeeId,Email,deleted) values (?,?,0)
Hibernate: insert into Emails (employeeId,Email,deleted) values (?,?,0)

4. Querying the Soft Deleted Records

Somewhere in the application, for example, while generating a report, if you wish to fetch all the rows which are marked soft deleted, then we can use the Native SQL queries. In this query, we can refer to the indication column and supply the appropriate indication value used according to strategy or converter value.

In the following example, we are creating a native SQL query that fetches all the soft deleted employees by using the ‘where e.deleted = 1‘ condition.

@Entity
@NamedNativeQueries({
  @NamedNativeQuery(
    name = "getInactiveEmployees",
    query = "SELECT id, firstName, lastName, email FROM Employee e where e.deleted = 1",
    resultClass=EmployeeEntity.class
  )
})
@SoftDelete
public class EmployeeEntity implements Serializable {

	//...
}

We can execute the native SQL query as follows and verify that the employee has been fetched successfully.

List<EmployeeEntity> deletedEmployees = entityManager
  .createNamedQuery("getInactiveEmployees", EmployeeEntity.class)
  .getResultList();

for (EmployeeEntity employee: deletedEmployees) {
  System.out.println(employee);
}

The program output:

Hibernate: SELECT id, firstName, lastName, email, deleted FROM Employee e where e.deleted = 1
EmployeeEntity{employeeId=1, email='admin@howtodoinjava.com', firstName='Lokesh', lastName='Gupta'}

5. Limitations

By not maintaining a field mapped (in Java class) to the database column for soft delete indicator, as we do with @SqlDelete, we do not have access to the indicator value in the Java code. This can be a big limitation when we are dealing with rows where some of the records can be deleted and some can be non-deleted.

So, if in your application, you do not have situations where you are working with all active/inactive rows at once, you should prefer to use @SoftDelete annotation as it handles the whole functionality transparently. It makes the code easier to read and maintain.

6. Conclusion

This tutorial discussed the soft delete functionality introduced in Hibernate 6.4 in detail. We discussed the usage of @SoftDelete annotation and its various attributes for managing the indication column name and indication values with examples.

We also discussed the limitations of @SoftDelete annotation in comparison to @SqlDelete annotation.

Happy Learning !!

Source Code on Github

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode