Tuesday, October 20, 2009

Hibernate Data Filters

In a lot of the applications I write, I prefer to use soft deletes rather than hard deletes to ensure that data can easily and quickly be restored if (and when) a user accidentally deletes something. However, trying to implement soft deletes is typically easier said than done. For example, if you wanted a query of all the non-deleted products, your query might look like:


<cfquery name="getProducts">
select *
from product
where isDeleted = 0
</cfquery>


To convert this to ColdFusion 9 ORM, you could write an HQL statement that looks like:


<cfset products = ORMExecuteQuery("from Product where isDeleted = 0") />


Obviously it isn't ideal replacing a simple entityLoad("Product") with HQL, but it becomes even worse when you consider having to recreate that logic across relationships. Yuck.

Introducing Hibernate Filters

"Hibernate3 has the ability to pre-define filter criteria and attach those filters at both a class level and a collection level. A filter criteria allows you to define a restriction clause similar to the existing "where" attribute available on the class and various collection elements. These filter conditions, however, can be parameterized. The application can then decide at runtime whether certain filters should be enabled and what their parameter values should be." Read more.

Long story short, you can define a filter that applies the isDeleted logic to your entities without actually touching your entities.

To accomplish this, I had my Product entity extend a base Entity in order to perform the save() and delete() methods, which are responsible for handling the soft deletes. After that, I updated my Product.hbmxml file to include the filter definition and add it to my Product entity. Finally, I updated Application.onRequestStart() to enable the softDeletes filter, which is disabled by default. Here's the sample code:

Application.cfc

component {

this.name = "sample";
this.datasource = "sample";
this.ormEnabled = true;
this.ormSettings.dbcreate = "update";
this.ormSettings.saveMapping = true;

public void function onRequestStart() {
if(structKeyExists(url,"init")) {
ormReload();
}
ORMGetSession().enableFilter("softDeletes");
}

}


Product.cfc

component persistent="true" extends="Entity" {

property id;
property name;

}


Entity.cfc

component accessors="true" {

property isDeleted;

public void function save() {
setIsDeleted(0);
entitySave(this);
}

public void function delete() {
setIsDeleted(1);
entitySave(this);
}

}


index.cfm

<cfset products = EntityLoad("Product") />

<cfdump var="#products#" />


Product.hbmxml

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>

<class entity-name="Product" lazy="true" name="cfc:sample.Product" table="Product">
<id name="id" type="int">
<column length="10" name="id"/>
<generator class="native"/>
</id>
<property name="name" type="string">
<column name="name"/>
</property>
<property name="isDeleted" type="boolean">
<column name="isDeleted"/>
</property>
<filter name="softDeletes" condition="isDeleted = 0" />
</class>

<filter-def name="softDeletes" />

</hibernate-mapping>


The really nice part about this approach is that I've got soft deletes working without my Product entity being aware, since that logic is hidden in the base class, a config file, and a global template.

8 comments:

  1. Tony this is a great post, however I do have one question.

    If you make a hcnage to the cfc entity, does the 2 get merged?

    ReplyDelete
  2. From what I've seen, if you're using the .hbmxml files to manage your entities, Hibernate will no longer pick up changes made to your CFC. For example, if I were to add a price property to my Product.cfc, I would need to manually add it the mapping to my pre-existing Product.hbmxml file in order for Hibernate to see it.

    ReplyDelete
  3. Can hibernate cascade soft deletes?

    ReplyDelete
  4. To be honest I don't know. A quick google search pulled up this link (http://bit.ly/2bEuum), but I'm not sure if that's what you're looking for.

    ReplyDelete
  5. Hi I'm reading the Hibernate Persistence book and trying to implement filters on my application. Everything seems right, but I can't seem to get it working. Everything else works file except when trying to set the filter parameter. Any ideas?

    ReplyDelete
  6. I remember having problems with where I put the <filter-def /> node in my .hbmxml file. If I put it before the <class /> nodes it would throw an error, but it works fine if I put it at the end.

    ReplyDelete
  7. Good post Tony. Its worth noting... when this.ormSetting.saveMapping is set to false you won’t have to worry about entity property changes overwriting your custom htmxml configuration and ColdFusion will continue to use the htmxml files for ORM mappings.

    ReplyDelete
  8. It is an incredible feature! Since the time I was looking for it!
    Tony thank you for the tip!

    It is a kind of constraint exists for the entity.

    The fact that the file hbmxml is not consistent with the fcc problem for me. I'd rather centralize information in the fcc.

    ReplyDelete