How to soft delete a record in Hibernate or JPA / Soft deletion of Entities in Hibernate or JPA

Soft Delete !!! What the heck ?

Soft delete means that the record is not removed from the database table but a flag is set to a value which indicates that this record should not be fetched while fetching the records from this table. The flag shall be a value of a column in the table itself.

For example, there is a deleted column in a table which has a value of false for a record which is newly inserted in the table but when a record is deleted, it is not removed from the table but the value of deleted column for this record is set to true. Now when fetching records from the table, only those records should be fetched which have a false in deleted column.

Soft Delete !!! Why ?

Soft delete has many practical scenarios. Suppose you have an application from which a user accidentally deleted some records through the UI of application or let’s say you want to give a rollback feature in your application. How will you retrieve accidentally deleted records or rollback delete action when there is nothing to retrieve or rollback !!! Soft delete is to the rescue. With soft delete you just update the flag of a record and the record is back to life.

Soft Delete !!! How ?

Now comes the main point, how do I implement soft delete. Before moving forward, let’s define an entity whose records we will be soft deleting. Entity class follows :

  @Entity
  @Table(name="users")
  public class User {
     @Id
     long id;
     @Column(name="username")
     String userName;
     @Column(name="country")
     String country;
     @Column(name="state")
     String state;
     @Column(name="age")
     int age;
    /* Getter and Setter methods follow */

 }

Now, in order to implement soft delete, first add a column to the database table and a corresponding field in the entity which will decide whether the entity record is marked as deleted or not. Updated entity is as follows :

  @Entity
  @Table(name="users")
  public class User {
     @Id
     long id;
     @Column(name="username")
     String userName;
     @Column(name="country")
     String country;
     @Column(name="state")
     String state;
     @Column(name="age")
     int age;
     /* 
     * Column that will hold delete decision
     */
     @Column(name="deleted")
     String deleteFlag;
    /* Getter and Setter methods follow */

 }

There may be two methods by which you are playing with entities in your application, that is,fetching, inserting or deleting entities. They are :

        • Using SQL (or HQL) queries
          If you are using SQL (or HQL) queries to delete and fetch the above entity in Hibernate (or pure jdbc) then the queries used would be :

          // for delete operation
          String query = "delete from users where id = 12";
          session.createSQLQuery(query).executeUpdate();
          entityManager.createQuery(query).executeUpdate();
          
          // for fetch operation
          String query = "select * from users";
          session.createSQLQuery(query).list(); // Hibernate
          entityManager.createQuery("Select u from User u").getResultList(); // JPA
          

          In order to add soft delete in this case, you just need to change your queries to set the flag column while deletion and fetch records based on the value of flag column. Updated queries would be :

          // for delete operation
          String query = "update users set deleted='true' where id = 12";
          session.createSQLQuery(query).executeUpdate(); // Hibernate
          entityManager.createQuery(query);
          // for fetch operation
          session.createSQLQuery("select * from users where deleted != 'false'").list(); // Hibernate
          entityManager.createQuery("Select u from User u where  deleted != 'false'").getResultList(); // JPA
          
      • Using Entities
        This is the case where you are directly operating over entities using the ORM framework methods where the fetch and delete would be like :

        /* DELETE OPERATION */
        session.delete(userObject); // Hibernate
        entityManager.remove(userObject); // JPA
        
        /* FETCH OPERATION */
        // :: For Single entity
        User user = (User)session.get(User.class, 12); // Hibernate
        User user = (User)entityManager.find(User.class, 12) // JPA
        
        // :: All Records of a table
        session.createCriteria(User.class).list(); // Hibernate
        entityManager.createQuery("Select u from User u")..getResultList(); // JPA
        

        In order to add soft delete in this case, you need to slightly change your entity class. You need to add a couple of annotations to it; One for setting flag while deletion and other for fetching entities selectively based on this flag. Updated class would look like:

          @Entity
          @Table(name="users")
          @SQLDelete(sql="Update users SET deleted = 'true' where id=?")
          @Where(clause="deleted != 'true'")
          public class User {
             @Id
             long id;
             @Column(name="username")
             String userName;
             @Column(name="country")
             String country;
             @Column(name="state")
             String state;
             @Column(name="age")
             int age;
             @Column(name="deleted")
             String deleteFlag;
            /* Getter and Setter methods follow */
        
         }
        

        Notice the two new annotations @SQLDelete and @Where added at the top, where

            • @SQLDelete – Provides the functionality to execute custom delete statement at the time of entity deletion. Whenever a delete call will be executed on an entity by the delete() method in Hibernate or remove() method in JPA, this query given inside this annotation’s sql attribute will be executed instead of a normal delete.
            • @Where – Provides a clause to add to the fetch operation of entities. The phrase given inside clause attribute of this annotation should be in SQL and will always be appended to the select query generated by Hibernate or JPA when entity is retrived using their methods. From Hibernate docs of this annotation (notice the last line. ) :

              ” Where clause to add to the element Entity or target entity of a collection. The clause is written in SQL. A common use case here is for soft-deletes. “

              Please note that these annotations will ONLY work when the entities are deleted / fetched using Hibernate or JPA specific methods (such as delete(), remove(), list()) and NOT when using SQL queries as in that case you already have the option to modify your queries.

              This method is fairly easy to implement and is flexible when using frameworks as the whole process is automated. Whenever an entity record is deleted and the entity is annotated with @SQLDelete, the entity record is not deleted but its column given in sql attribute is updated. Similarly at the time of fetch, only those records are fetched which have the required value of column given in clause attribute of @Where annotation.

        Let’s tweak in :

              1. If you are using an ORM framework, then most of the times you will be using entity level operations, that is, using second approach. In that case, it is recommended to use the above mentioned annotations.
              2. @Where becomes mandatory when using @SQLDelete , as in absence of no where clause all the entities (even those marked as deleted) will be fetched.
              3. @Where may also be applied over getter method of child entities in a One-To-Many relationship in a parent entity in which case the condition given in clause attribute of this annotation will be executed when fetching child entities.
              1. Suppose you have an entity which contains another entity list (that is, there is a One-To-Many relationship). Obviously when you are soft deleting the parent entity you will require the child entities also to be soft deleted. In that case you also need to add @SQLDelete annotation to the child entity and your purpose will be achieved.
              2. Continuing with the above scenario, what if the child entity class has no field to act as a flag and its database table has no flag column and your application design prevents you from adding one. In that case, just modify your cascade attribute applied over the child entity collection. Remove CASCADETYPE.ALL option and change it to cascade = { CascadeType.PERSIST, CascadeType.MERGE }.

        At this line… Bravo !!! You have read the entire post. Don’t just go away without showing your presence here. Comment, feedback or share this post to be a starter.

4 Comments

Leave a Reply