Soft deletes using Hibernate annotations.

blog-exampleI am currently working on a Seam application that has a need for soft deletes in the database. To the right you can see a snippet of my database diagram which contains a CUSTOMER and APP_USER table. This is just a straight forward one to many relationship but the important thing to note though is the “DELETED” field in each table. This is the field that will be used to track the soft delete. If the field contains a ‘1′ the record has been deleted and if it contains a ‘0′  the record hasn’t been deleted.

Before ORMs like Hibernate I would have had to track and set this flag myself using SQL. It wouldn’t be super hard to do but who wants to write a bunch of boilerplate code just to keep track of whether or not a record has been deleted. This is where Hibernate and annotations comes to the rescue.

Below are the 2 Entity classes that were generated by Hibernate using seamgen. I have omitted parts of the code for clarity.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//Package name...

//Imports...

@Entity
@Table(name = "CUSTOMER")
//Override the default Hibernation delete and set the deleted flag rather than deleting the record from the db.
@SQLDelete(sql="UPDATE customer SET deleted = '1' WHERE id = ?")
//Filter added to retrieve only records that have not been soft deleted.
@Where(clause="deleted <> '1'")
public class Customer implements java.io.Serializable {
private long id;
private Billing billing;
private String name;
private String address;
private String zipCode;
private String city;
private String state;
private String notes;
private char enabled;
private char deleted;
private Set appUsers = new HashSet(0);

//Constructors...

//Getters and Setters...

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "customer")
//Filter added to retrieve only records that have not been soft deleted.
@Where(clause="deleted <> '1'")
public Set getAppUsers() {
return this.appUsers;
}
public void setAppUsers(Set appUsers) {
this.appUsers = appUsers;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//Package name...

//Imports...

@Entity
@Table(name = "APP_USER")
//Override the default Hibernation delete and set the deleted flag rather than deleting the record from the db.
@SQLDelete(sql="UPDATE app_user SET deleted = '1' WHERE id = ?")
//Filter added to retrieve only records that have not been soft deleted.
@Where(clause="deleted <> '1'")
public class AppUser implements java.io.Serializable {
private long id;
private Customer customer;
private AppRole appRole;
private char enabled;
private String username;
private String appPassword;
private Date expirationDate;
private String firstName;
private String lastName;
private String email;
private String phone;
private String fax;
private char deleted;
private Set
persons = new HashSet(0);

//Constructors...

//Getters and Setters...
}

The following 2 steps is all that I had to do to implement the soft delete.

  1. Added the @SQLDelete annotation which overrides the default Hibernate delete for that entity.
  2. Added the @Where annotation to filter the queries and only return records that haven’t been soft deleted. Notice also that in the CUSTOMER class I added an @Where to the appUsers collection. This is needed to fetch only the appUsers for that Customer that have not been soft deleted.

Viola! Now anytime you delete those entities it will set the “DELETED” field to ‘1′ and when you query those entities it will only return records that contain a ‘0′ in the “DELETED” field.

Hard to believe but that is all there is to implementing soft deletes using Hibernate annotations.



Comments

  1. gilad bar orion May 4th

    Comment Arrow

    Hi
    Great post! wasn’t aware of ‘@sqlDelete’, thanks!

    also note that instead of using the @Where(clause=”deleted ‘1′”) statements you can use hibernate filter (http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-hibspec-filters) to globally filter-out all ‘deleted’ entities. I found that defining 2 entity managers (‘normal’ one that filter deleted items, and one that doesn’t, for the rare cases…) is usually quite convenient.


  2. Brian May 4th

    Comment Arrow

    Glad the post helped you. Never thought about using 2 Entity Managers. Will keep that in mind. Thanks for the tip.


  3. Anitha May 25th

    Comment Arrow

    Hi,
    I have a same scenario. I want use soft delete in my seam application and i’m new to hibernate and seam. After reading your post i got to know that using hibernate annotations one can provide soft delete functionality.

    I understood how to use the annotations in java files. But should i be making any changes in any of the *.xhtml files? I’m not able to proceed. Please help me by explaining in detail.


  4. Brian May 25th

    Comment Arrow

    Anitha – That is all you have to do. Now when you delete one of the entities Hibernate will set the flag rather than deleting the item from the database.

    You should never try and implement this logic in the view or controller. It should always be in the model.


  5. Anitha May 27th

    Comment Arrow

    Thanks Brian,

    Its working as expected.. Thanks a lot


  6. Brian May 27th

    Comment Arrow

    Glad to hear it is working Anitha.


  7. John Logan July 23rd

    Comment Arrow

    @Where unfortunately does not work with entityManager.find()


  8. Brian July 29th

    Comment Arrow

    John – Not sure on that one. I thought JPA supported the @Where but I will double check. Thanks for the heads up.


  9. Shane August 13th

    Comment Arrow

    If you are using a version property that value will be added to the params so your annotation will look something like this:

    @SQLDelete(sql=”UPDATE table_name SET hidden = ‘1′, updated = ? WHERE id = ?”)

    Unfortunately the params are in the wrong order i.e. the id is first and the version is second…do you know any way to fix this?


  10. Brian August 16th

    Comment Arrow

    Shane – I may be understanding correctly what you asking but:

    The application must not alter the version number set up by Hibernate in any way. To artificially increase the version number, check in Hibernate Entity Manager’s reference documentation LockModeType.OPTIMISTIC_FORCE_INCREMENT or LockModeType.PESSIMISTIC_FORCE_INCREMENT.


  11. Shane August 16th

    Comment Arrow

    Brian – there are 2 params being passed into the prepared statement – in my case a Date (I am using a date for version) and a Long (the primary key of the record).

    If you use sql=”UPDATE table_name SET hidden = ‘1′ WHERE id = ?” there is a param index error thrown. So you have to use both of the params – they are just out of order to be of much help. The Date is second so it must be in or after the WHERE clause. Here is what I ended up with.

    sql=”UPDATE client SET hidden = 1, updated = now() where id = ? and 0 ?”

    It works fine but I was hoping there would be either a fix in Hibernate or another way of reordering the params.


  12. Shane August 16th

    Comment Arrow

    the not equals sign was zapped out of my last post.
    sql=”UPDATE client SET hidden = 1, updated = now() where id = ? and 0 != ?” to replace the operator with java


  13. Shane August 16th

    Comment Arrow

    See:http://opensource.atlassian.com/projects/hibernate/browse/HHH-4083


  14. Brian August 17th

    Comment Arrow

    Shane – I always use database triggers for the DATE_CREATED and DATE_UPDATED fields in the database. I have it is as a standard for all of my tables. Then I don’t have to worry about it Hibernate or in my code.


Add Yours

  • Author Avatar

    YOU


Comment Arrow



About Author

Brian

Brian Abston is an IT Manager in Oklahoma that also still gets his hands dirty and writes Seam code. He also dabbles in Ruby and Rails. He loves to code and hates to manage. Jack of all trades and master of some.