Use Hibernate to implement soft deletion

When we do the project, we often encounter such a demand: the user wants to delete the data is not really from the physical file to delete the record but mark the record, the next time to check only those who only check Marked as deleted records, that is, we often say “false delete”. The reason for this is because the user wants to keep the history of the data as much as possible for future tracing, or the existing records associated with the data of other tables, if deleted will lead to data integrity issues. In general, there are two ways to achieve soft deletion, the first is to delete the record written to the audit log or transferred to other tables, this has some limitations, after the view is not convenient, and even And other tables to re-establish the relationship between the more trouble; another way is to add an additional field for the table, such as Boolean, enumeration to represent the status of the data. The following is to use Hibernate to achieve the second way to achieve the soft deletion.

To achieve soft removal, we need to overcome two difficulties:

  • When you encounter delete operations, we need to let Hibernate does not generate DELETE statement, but generate UPDATE statement and update the corresponding status field
  • When doing any queries, we want Hibernate to automatically filter out the data based on the flag field, because our system usually has a large number of query methods, if there is no automatic mechanism, then we need to add all the query methods filter conditions, obviously Very troublesome is also prone to error, if the leak will be exposed to the data.

Here are the code to demonstrate how to achieve the soft delete, the demo environment is: JPA / Hibernate 5.2.6.Final, MySQL 5.6.26, the project specific configuration is as follows:

Java <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.6.Final</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.22</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.8</version> </dependency> </dependencies>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <dependencies>      <dependency>          <groupId> org . hibernate < / groupId >          <artifactId> hibernate core < / artifactId >          <version> 5.2.6.Final < / version >      < / dependency >      <dependency>        <groupId> mysql < / groupId >          <artifactId> mysql connector java < / artifactId >          <version> 5.1.40 < / version >      < / dependency >      <dependency>          <groupId> org . slf4j < / groupId >          <artifactId> slf4j api < / artifactId >          <version> 1.7.22 < / version >      < / dependency >      <dependency>          <groupId> ch . qos . logback < / groupId >          <artifactId> logback classic < / artifactId >          <version> 1.1.8 < / version >      < / dependency > < / dependencies >

First we define a simple entity Student class:

Java @Entity @Table (name = “student”) public class Student {/ ** * primary key * / @Id @GeneratedValue (strategy = GenerationType.IDENTITY) private Integer id; / ** * name * / @Column (name = name_ “, length = 20) private String name; / ** * whether to delete (flag bit) * / @Column (name =” is_deleted_ “) private Boolean isDeleted;}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Entity @Table ( name = “student” ) public class Student {      /**         * primary key      */      @Id      @GeneratedValue ( strategy = GenerationType . IDENTITY )      private Integer id ;      /**         * Name      */      @Column ( name = “name_” , length = 20 )      private String name ;      /**         * whether to delete (marked bit)      */      @Column ( name = “is_deleted_” )      private Boolean isDeleted ; }

The above code, for the sake of convenience we let the main key from the increase, the entity there are two other simple fields, namely the name and delete the flag, which isDeleted field default is false, and now we want to achieve the goal is When we use JPA to delete a student’s record, the generated SQL statement is not DELETE but updates the is_deleted_ field and updates it to true.
Want to achieve the above objectives, we need to re-implement the Hibernate delete operation, and Hibernate provides us with a comment @ SQLDelete, we only need to specify the deletion of the operation to specify the original SQL, the SQL will cover Hibernate The original generated DELETE statement to achieve the purpose of rewriting delete operation, the specific implementation of the following look at the code:

Java @Entity @Table(name = “student”) @SQLDelete(sql = “update student set is_deleted_ = true where id=?”, check = ResultCheckStyle.COUNT) public class Student {…}
1 2 3 4 @Entity @Table ( name = “student” ) @SQLDelete ( sql = “update student set is_deleted_ = true where id=?” , check = ResultCheckStyle . COUNT ) public class Student { . . . }

In the above code, we use the @SQLDelete annotation on the Student entity and set up a native SQL to update the status bits. Here’s whether we can see the soft deletion under test:

Java EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(“pu”); EntityManager entityManager = entityManagerFactory.createEntityManager(); entityManager.getTransaction().begin(); Student student = entityManager.find(Student.class, 3); entityManager.remove(student); entityManager.getTransaction().commit(); logger.info(“{}”, student); entityManager.close(); entityManagerFactory.close();
1 2 3 4 5 6 7 8 9 EntityManagerFactory entityManagerFactory = Persistence . createEntityManagerFactory ( “pu” ) ; EntityManager entityManager = entityManagerFactory . createEntityManager ( ) ; entityManager . getTransaction ( ) . begin ( ) ; Student student = entityManager . find ( Student . class , 3 ) ; entityManager . remove ( student ) ; entityManager . getTransaction ( ) . commit ( ) ; logger . info ( “{}” , student ) ; entityManager . close ( ) ; entityManagerFactory . close ( ) ;

The above code we first check out the ID of 3 records, and then delete and submit the transaction, and finally print out the query just out of the record, the following is the Hibernate generated log:

Java 16:47:28.717 [main] DEBUG org.hibernate.SQL – select student0_.id as id1_0_0_, student0_.is_deleted_ as is_delet2_0_0_, student0_.name_ as name_3_0_0_ from student student0_ where student0_.id=? 16:47:28.735 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder – binding parameter [1] as [INTEGER] – [3] 16:47:28.748 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor – extracted value ([is_delet2_0_0_] : [BOOLEAN]) – [false] 16:47:28.748 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor – extracted value ([name_3_0_0_] : [VARCHAR]) – [abc] 16:47:28.773 [main] DEBUG org.hibernate.SQL – update student set is_deleted_ = true where id=? 16:47:28.774 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder – binding parameter [1] as [INTEGER] – [3] 16:47:28.780 [main] INFO org.mingzhe.test.SoftDeleteTest – Student{id=3, name=’abc’, isDeleted=false}
1 2 3 4 5 6 7 16 : 47 : 28.717 [ main ] DEBUG org . hibernate . SQL select student0_ . id as id1_0_0_ , student0_ . is_deleted_ as is_delet2_0_0_ , student0_ . name_ as name_3_0_0_ from student student0_ where student0_ . id = ? 16 : 47 : 28.735 [ main ] TRACE org . hibernate . type . descriptor . sql . BasicBinder binding parameter [ 1 ] as [ INTEGER ] [ 3 ] 16 : 47 : 28.748 [ main ] TRACE org . hibernate . type . descriptor . sql . BasicExtractor extracted value ( [ is_delet2_0_0_ ] : [ BOOLEAN ] ) [ false ] 16 : 47 : 28.748 [ main ] TRACE org . hibernate . type . descriptor . sql . BasicExtractor extracted value ( [ name_3_0_0_ ] : [ VARCHAR ] ) [ abc ] 16 : 47 : 28.773 [ main ] DEBUG org . hibernate . SQL update student set is_deleted_ = true where id = ? 16 : 47 : 28.774 [ main ] TRACE org . hibernate . type . descriptor . sql . BasicBinder binding parameter [ 1 ] as [ INTEGER ] [ 3 ] 16 : 47 : 28.780 [ main ] INFO   org . mingzhe . test . SoftDeleteTest Student { id = 3 , name = ‘abc’ , isDeleted = false }

From the fifth line of the log we can see that the delete operation has become our specified update statement, that our purpose is achieved, but there have been new problems, we can see the last line of the log shows our entity manager student The isDeleted field is still fasle. Why is this? This is because we have implemented native SQL, and Hibernate is unable to detect the original statement in the end which records have been changed, so it can not manage the context of the entity state to update, so we need to improve, The entity status is updated. For this problem, the more elegant way is to use JPA’s lifecycle approach, we define a method in the Student class and use the @PostRemove annotation to tell Hibernate to update the state of the entity after the delete operation is performed, as follows:

Java @Entity @Table(name = “student”) @SQLDelete(sql = “update student set is_deleted_ = true where id=?”, check = ResultCheckStyle.COUNT) public class Student { @PostRemove public void delete() { this.isDeleted = true; } }
1 2 3 4 5 6 7 8 9 @Entity @Table ( name = “student” ) @SQLDelete ( sql = “update student set is_deleted_ = true where id=?” , check = ResultCheckStyle . COUNT ) public class Student {      @PostRemove      public void delete ( ) {          this . isDeleted = true ;      } }

From the above code we can see that when the implementation of the deletion of the implementation of JPA will automatically call delete () method will delete the entity’s isDeleted field is set to true

Here we have to solve is how to implement the query automatically exclude those who have been marked as deleted records. This time we still use a Hibernate private note – @ Where, this annotation is used in each generated WHERE statement is automatically followed by our specified filter conditions, the following code demonstrates through the annotation according to the status field to automatically filter data:

Java @Entity @Table(name = “student”) @SQLDelete(sql = “update student set is_deleted_ = true where id=?”, check = ResultCheckStyle.COUNT) @Where(clause = “is_deleted_ != true”) public class Student {…}
1 2 3 4 5 @Entity @Table ( name = “student” ) @SQLDelete ( sql = “update student set is_deleted_ = true where id=?” , check = ResultCheckStyle . COUNT ) @Where ( clause = is_deleted_    != true ) public class Student { . . . }

In the above code we use the @Where annotation on the entity class and specify that the filter is is_deleted_ is not equal to true. Each condition is automatically added to the statement after each query. Here’s a query to see if Get the correct result:

Java EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(“pu”); EntityManager entityManager = entityManagerFactory.createEntityManager(); entityManager.getTransaction().begin(); Student student1 = entityManager.find(Student.class, 3); logger.info(“{}”, student1); Student student2 = entityManager.find(Student.class, 4); logger.info(“{}”, student2); entityManager.getTransaction().commit(); entityManager.close(); entityManagerFactory.close();
1 2 3 4 5 6 7 8 9 10 EntityManagerFactory entityManagerFactory = Persistence . createEntityManagerFactory ( “pu” ) ; EntityManager entityManager = entityManagerFactory . createEntityManager ( ) ; entityManager . getTransaction ( ) . begin ( ) ; Student student1 = entityManager . find ( Student . class , 3 ) ; logger . info ( “{}” , student1 ) ; Student student2 = entityManager . find ( Student . class , 4 ) ; logger . info ( “{}” , student2 ) ; entityManager . getTransaction ( ) . commit ( ) ; entityManager . close ( ) ; entityManagerFactory . close ( ) ;

The above code, we were queryed for the record of id 3,4, and print the query results, which is 3 for the record delete flag bit has been set to true, so print out student1 is null, and student2 is the normal query out, Indicating that our settings work, the following is the generated log:

Java 08: 25: 50.864 [main] DEBUG org.hibernate.SQL – select student0_.id as id1_0_0_, student0_.is_deleted_ as is_delet2_0_0_, student0_.name_ as name_3_0_0_ from student student0_ where student0_.id =? And (student0_.is_deleted_! = 1 ) 08: 25: 50.885 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder – binding parameter [1] as [INTEGER] – [3] 08: 25: 50.896 [main] INFO org.mingzhe.test. Retrieve StudentD_0 ! = 1) 08: 25: 50.897 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder – binding parameter [1] as [INTEGER] – [4] 08: 25: 50.918 [main] TRACE org.hibernate type [ ]: [VA RjAR]) – [zhang three] 08: 25: 50.925 [main] INFO org.mingzhe.test.SoftDeleteTest – Student {id = 4, name = ‘zhang three’, isDeleted = false}
1 2 3 4 5 6 7 8 08 : 25 : 50.864 [ main ] DEBUG org . hibernate . SQL select student0_ . id as id1_0_0_ , student0_ . is_deleted_ as is_delet2_0_0_ , student0_ . name_ as name_3_0_0_ from student student0_ where student0_ . id = ? and ( student0_ . is_deleted_ != 1 ) 08 : 25 : 50.885 [ main ] TRACE org . hibernate . type . descriptor . sql . BasicBinder binding parameter [ 1 ] as [ INTEGER ] [ 3 ] 08 : 25 : 50.896 [ main ] INFO   org . mingzhe . test . SoftDeleteTest null 08 : 25 : 50.897 [ main ] DEBUG org . hibernate . SQL select student0_ . id as id1_0_0_ , student0_ . is_deleted_ as is_delet2_0_0_ , student0_ . name_ as name_3_0_0_ from student student0_ where student0_ . id = ? and ( student0_ . is_deleted_ != 1 ) 08 : 25 : 50.897 [ main ] TRACE org . hibernate . type . descriptor . sql . BasicBinder binding parameter [ 1 ] as [ INTEGER ] [ 4 ] 08 : 25 : 50.918 [ main ] TRACE org . hibernate . type . descriptor . sql . BasicExtractor extracted value ( [ is_delet2_0_0_ ] : [ BOOLEAN ] ) [ false ] 08 : 25 : 50.920 [ main ] TRACE org . hibernate . type . descriptor . sql . BasicExtractor extracted value ( [ name_3_0_0_ ] : [ VARCHAR ] ) [ Zhang three ] 08 : 25 : 50.925 [ main ] INFO   org . mingzhe . test . SoftDeleteTest Student { id = 4 , name = ‘Zhang three’ , isDeleted = false }

From the log can be confirmed in the implementation of the query process Hibernate will automatically generate statements for the addition of our designated filter conditions.

Some students may ask if I want to check all the records of what? In this case we can define a special method, with the original SQL to query out all the records. This article explains the use of Hibernate to achieve the soft delete is actually using the Hibernate private notes, although it can be very elegant to complete the goal, but there are some drawbacks, such as non-portable, if we have a better way to welcome advice.

Using the hibernate annotation, how to identify a property that is a non-database field

Use the hibernate annotation, how to identify a property non-database field.
That is, an entity class corresponds to a table, but this entity class does not correspond to a field in the field

@Transient
        Optional
      @Transient indicates that the attribute is not a mapping to the field of the database table, and the ORM framework ignores the attribute.
      If a property is not a field map of a database table, it must be marked as @Transient, otherwise, the ORM framework defaults to @Basic
        Example:
        // calculate the age attribute according to birth
    @Transient
    public int getAge() {
       return getYear(new Date()) – getYear(birth);
    }

If the database is mapped, the fields need to add annotations

 @Column(name = “PARAMNM”, length = 50)
 public String getParamNm()
 {
  return this.paramNm;
 }

Table id need to add

@Id
 @GeneratedValue
 @Column(name = “ID”, unique = true, nullable = false, precision = 22, scale = 0)
 public Long getId()
 {
  return this.id;
 }

Use Spring custom annotations to implement task routing

In the development of Spring mvc, we can use RequestMapping to match the current method to deal with which URL request. Similarly, we now have a demand, there is a task scheduler, according to the different types of tasks routing to different tasks Device. Its essence is through the external parameters of a routing and Spring mvc to do something similar. Simply read the Spring mvc implementation principle, decided to use the custom annotation to achieve the above functions.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface TaskHandler {

    String taskType() default "";
}

Task processor definition

The above defines a task to execute the processor, and all other concrete task executors inherit the implementation of this method. Where Task represents the definition of the task, including the task Id, the parameters required to perform the task.

Next, we can implement a specific task processor.

Above we achieve a user name to modify the notice of the task processor, the specific business logic here is not achieved.

Task Handler Registration

Here inherited the Spring ApplicationObjectSupport class, the specific registration process is as follows

Task execution

public class TaskExecutor implements Job {

    private static final String TASK_TYPE = "taskType";

    @Override
    public BaseResult execute(Task task){
        String taskType=task.getTaskType();
        if (TaskHandlerRegister.getTaskHandler(taskType) == null) {
            throw new RuntimeException("can't find taskHandler,taskType=" + taskType);
        }
        AbstractTaskHandler abstractHandler = TaskHandlerRegister.getTaskHandler(taskType);
        return abstractHandler.execute(task);
    }
}
  1. Verify the type of task, there is no registration in the registry related Handler
  2. Handelr from the task registration center to the corresponding processing
  3. Execute the Handelr