AdBlock Detected

It looks like you're using an ad-blocker!

Our team work realy hard to produce quality content on this website and we noticed you have ad-blocking enabled. Advertisements and advertising enable us to continue working and provide high-quality content.

Soft Delete in Spring Boot


In this new Refactorizando article, we will see the Soft Delete in Hibernate and Spring Boot with an example, using annotations. We will use @SQLDelete for this purpose.

What is Soft Delete?

We can define a soft delete as a logical deletion of a record in a table by updating a field. In other words, our table will have a field that acts as a deletion flag.

This way, we won’t physically delete the record, but only mark it so that it doesn’t appear in searches.

Soft Delete Example

We will define the necessary classes to check for a soft delete. You can use H2 as an in-memory database.

Defining the Database Object

package com.refactorizando.soft.delete;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "car")
public class car {

  @Id
  @GeneratedValue
  private Long id;
  
  private String model;
  
  private String color;
  
  private Boolean deleted;
}

We have defined a deleted field that will be updated and serve as a logical deletion.

Controller definition

Next, let’s define the methods in our API:

package com.refactorizando.soft.delete;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
public class carController {

  private final CarService carService;
  
  @PostMapping(value = "/save")
  public ResponseEntity<Car> save(@RequestBody Car car) {
    return new ResponseEntity<>(carService.save(car), HttpStatus.OK);
  }
  @DeleteMapping("/delete/{id}")
  public void delete(@PathVariable Long id) {
    carService.delete(id);
  }
  @GetMapping(value = "/list")
  public ResponseEntity<List<Car>> findAll() {
    List<Car> cars = carService.findAll();
    return new ResponseEntity<>(cars, HttpStatus.OK);
  }
}

@SQLDelete

@SQLDelete is an annotation provided by Hibernate that allows us to perform a logical deletion when the JPA delete method is invoked.

This annotation can receive three input parameters:

  • sql: Procedure name or SQL UPDATE/DELETE statement.
  • callable: Is the statement callable
  • check: For persistence operation, what style of determining results (success/failure) is to be used

Let’s apply this annotation to our class:

package com.refactorizando.soft.delete;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "car")
@SQLDelete(sql = "UPDATE car SET deleted=true WHERE id = ?")
public class car {

  @Id
  @GeneratedValue
  private Long id;
  
  private String model;
  
  private String color;
  
  private Boolean deleted;
}

We have created an update of the ‘deleted’ field, and each time the delete(T) method is invoked, the update will be executed instead of deletion. The delete method belongs to the CrudRepository interface.

If we invoke the delete method from our API and then the findAll() method, we get:

Output:

[
  {
   "id" : 1,
   "model": "MAZDA",
   "color": "RED",
   "deleted": "false",
  },
  {
   "id" : 2,
   "model": "SEAT",
   "color": "BLUE",
   "deleted": "true",
  }
]

That is, it has shown us all the records from our database, but if we have a deleted record, it should not appear. For that, we have the @Where annotation.

@Where

The annotation @Where allows us to set a filter when displaying our object:

package com.refactorizando.soft.delete;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "car")
@SQLDelete(sql = "UPDATE car SET deleted=true WHERE id = ?")
@Where(clause = "deleted = false")
public class car {

  @Id
  @GeneratedValue
  private Long id;
  
  private String model;
  
  private String color;
  
  private Boolean deleted;
}

With the @Where clause = “deleted = false”, we will have the following results:

Output:

[
  {
   "id" : 1,
   "model": "MAZDA",
   "color": "RED",
   "deleted": "false",
  }  
]

Showing Soft Deleted Records Dynamically

Sometimes we need to display deleted or non-deleted records based on a condition. For this purpose, we can use @Filter and @FilterDef.

@FilterDef

@FilterDef annotation defines the requirements to use with @Filter:

  • name: The name of the filter to use in @Filter and in session (we’ll see it in the example).
  • parameters: We can define parameters using @ParamDef, and define name and type.

@Filter

@Filter uses the definition made in the @FilterDef annotation using the defined parameter name. It receives the following parameters:

  • name: The name defined in @FilterDef.
  • condition: The condition to apply the filter based on the parameter.

Let’s see an example of how @Filter would work. The idea is to pass a parameter from our controller to show us the deleted or non-deleted records. To do this, we will modify the Car class by adding @Filter and the findAll() method. We will also add a new method to set the defined filter with the incoming parameter in the session.

@Entity
@Getter
@Setter
@Table(name = "car")
@SQLDelete(sql = "UPDATE car SET deleted=true WHERE id = ?")
@FilterDef(
        name = "deletedCarFilter",
        parameters = @ParamDef(name = "isDeleted", type = "boolean")
)
@Filter(
        name = "deletedCarFilter",
        condition = "deleted = :isDeleted"
)
public class car {
...
}

We have removed the @Where annotation because using both annotations simultaneously is not compatible.

@GetMapping(value = "/list")
public ResponseEntity<List<Car>> findAll(@RequestParam(value = 
    "isDeleted", required = false, defaultValue = "false")boolean 
     isDeleted {

    List<Car> cars = carService.findAllFilter(isDeleted);

    return new ResponseEntity<>(cars, HttpStatus.OK);
}

public List<Car> findAllFilter(boolean isDeleted) {
    Session session = entityManager.unwrap(Session.class);
    Filter filter = session.enableFilter("deletedCarFilter");
    filter.setParameter("isDeleted", isDeleted);
    List<Car> cars = carRepository.findAll();
    session.disableFilter("deletedCarFilter");
    return cars;
}

If we execute the findAll() method now, we will obtain the following results based on the parameters:

Execution:

localhost:8080/cars/list/?isDeleted=true

Output:

[  
  {
   "id" : 2,
   "model": "SEAT",
   "color": "BLUE",
   "deleted": "true",
  }
]

Execution:

localhost:8080/cars/list/?isDeleted=false

Output:

[
  {
   "id" : 1,
   "model": "MAZDA",
   "color": "RED",
   "deleted": "false",
  }  
]

Benefits of Soft Delete

  1. Data Integrity: Soft Delete preserves the record’s existence without actually deleting it, ensuring that data relationships and dependencies are maintained.
  2. Auditing and History: Soft Delete allows for tracking and auditing of record deletions, as the deletion is performed through an update operation.
  3. Performance Optimization: Soft Delete avoids the need for costly and time-consuming physical delete operations, improving overall system performance.

Conclusion

In this post, we have seen how Soft Delete works in Hibernate and Spring Boot to avoid having to perform updates on the object we want to logically delete.

If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!

Leave a Reply

Your email address will not be published. Required fields are marked *