Recently our development team came upon the need to develop a Grails 3 plugin that implements logical delete for entities. A “logical” delete (sometimes referred to as a “soft” delete) is a delete that doesn’t actually delete the relevant data but instead marks the data as deleted. Marking the data as deleted has the benefit of excluding the data from queries by default while still maintaining the ability to retrieve the data when/if necessary. Essentially the plugin offers a mechanism to “hide” domain objects from retrieval. This is useful for retaining data without having it clutter the current set of active data. Logical Delete functionality is available in a few Grails 2 plugins. However these implementations rely on filters, ASTs, and runtime metaClass enhancements. We decided to create another implementation of the logical delete using some of latest Grails 3 features such as Traits and Listeners.
This blog will highlight how to use the plugin and also give some insight into the techniques used under the covers.
Logical delete of an entity has a few relevant use cases in enterprise applications. Companies may want to “delete” data from their everyday usage, but still keep it for later retrieval. Auditing requirements for financial institutions may require the data to be sustained for up to seven years. From a technical perspective, if a domain model has several complex associations that make a chain of domain objects, hard deletion may cause slow cascading affects or be blocked by referential integrity. In these situations, a logical delete alleviates these concerns.
To add the GORM Logical Delete plugin to an application add the following dependency to the dependencies block of your
Enable Domain entity with Logical Delete
Any domain entity can be implemented with the
LogicalDelete trait. The trait adds a boolean persistent property named
to the domain class. The property is used to “hide” entities from queries if it is set to
true. Note the mapping columns
can customize the property to a database column name.
In order to delete a domain object enabled with logical delete, simply use the same GORM interface as usual.
If you would like to physically delete the record from persistence, use the attribute
Undelete functionality is quite handy if you want to reverse the property to false.
When an object is enabled with logical delete, queries associated with the domain object will hide those marked with
deleted = true.
Dynamic Finders, Criteria Query, Detached Criteria Query, and the
GormEntity<D> methods like
read are all supported.
See the Query documentationfor a list of examples.
Note Hibernate Criteria and HQL queries are NOT supported by this plugin as they are ORM implementation specific.
Behind The Scenes
It’s beneficial to understand how the plugin is implemented under the covers. We have tried to use the most efficient techniques with Groovy and Grails, which reduces some of the noise and clutter found in previous plugin implementations.
In concept, what is occurring is that any domain object can be given a logical delete capability with the attribute
When the entity is logically deleted, it is set to
true. It is not physically deleted from persistence.
During query time, a query event is intercepted by a listener and the query is altered to only include
deleted = false items from the result set.
This gives the illusion that the items are not present, but in reality they are just hidden by the query.
LogicalDelete trait makes available a
deleted attribute. As stated earlier, an entity can implement this.
LogicalDelete trait has overridden static methods which take into account the
deleted property in an altered query.
This allows the client to use the
GormEntity<D> methods to essentially hide those items.
ApplicationListener and when the event fires, it will add a
false to the query.
IGNORE_DELETED_FILTER should also be noted. When set to
true, then all entities that exist
in the database are included in the query. In other words, it bypasses the logical delete flag all together. In
note how this
ThreadLocal variable is used in the
withDeleted method. The method is passed in a closure (which is a query)
and the variable is set to
true, which bypasses the delete flag.
essentially achieves the same goal, but is intended as an implementation for an annotation.
The spock tests contained in the plugin are a good resource to reference examples and usages of the plugin API.
Take a look and we hope the plugin is useful for your needs.