1. Introduction

The Logical Delete plugin provides support for soft deleting records.

A logical or soft deletion, is one that doesn’t actually delete the record from persistent storage, but instead marks the record as deleted.

Marking the record as deleted has the benefit of maintaining the ability to retrieve the record if necessary.

Essentially, the plugin offers a mechanism to hide records from retrieval. This is useful for retaining records without having it clutter the current set of active data.

2. Installation

2.1. Adding the Plugin to Your Grails Application

To add this plugin to your Grails application, include the following dependency in the dependencies block of the build.gradle file:

implementation 'org.grails.plugins:grails-logical-delete:3.0.0-SNAPSHOT'

2.2. Snapshot Builds

You can also use snapshot builds, as they are published to the Grails Artifactory Repository.

3. Usage

3.1. The LogicalDelete Trait

This plugin provides the LogicalDelete trait, which any Grails domain class can implement to enable soft deletes.

The trait adds a persistent boolean property named deleted to the domain class.

When deleted is set to true, the entity is considered logically deleted and will be excluded from query results by default.

import grails.logical.delete.LogicalDelete

class Person implements LogicalDelete<Person> {

    String userName

    static mapping = {
        // the deleted property may be configured
        // like any other persistent property...
        deleted(column: 'delFlag')
    }
}

3.2. Soft Deleting Entities

Deleting an entity that implements the LogicalDelete trait is as simple as calling the delete() method. This sets the deleted property to true, causing the record to be logically deleted — meaning it will be excluded from query results by default.

Person p = new Person(userName: 'Nirav').save()
p.delete()

To restore (or undelete) the object, call the undelete() method:

p.undelete()

If you want to physically delete the record from persistent storage, pass the hard: true argument to the delete() method:

p.delete(hard: true)

3.3. Queries

Most queries automatically exclude logically deleted records from their results by default.

Hibernate Criteria and HQL queries are NOT supported by this plugin.

For example, a dynamic finder query written as:

results = Person.findAll {
    userName == 'Robert'
}

is executed as if it had been written like this:

results = Person.findAll {
    userName == 'Robert'
    deleted == false
}

If you need to include logically deleted records in query results, wrap your query in a call to withDeleted, as shown below. Any query can be wrapped with the withDeleted closure.

results = Person.withDeleted { Person.findAll() } as List<Person>

The plugin supports a variety of query types in addition to dynamic finders.
Examples are shown below:

3.3.1. Criteria Query

def criteria = Person.createCriteria()
def results = criteria.list {
    or {
        eq('userName', 'Ben')
        eq('userName', 'Nirav')
    }
}

3.3.2. Detached Criteria Query

def results = Person.where {
    userName == 'Ben' || userName == 'Nirav'
}.list()

3.3.3. GORM methods

The standard GormEntity<D> methods — such as get, load, proxy, and read — are also supported.

3.3.4. GORM Data Services

GORM Data Services are supported. See the example below for an example.

3.4. WithDeleted Annotation

The @WithDeleted annotation allows you to mark a method so that queries executed within it include logically deleted records. It serves as an alternative to using the withDeleted closure.

This is particularly useful when working with GORM Data Services or other classes that function as Data Access Objects (DAOs).

@Service(Person)
interface PeopleDataService {

    List<Person> listPeople()

    @WithDeleted
    List<Person> listPeopleWithDeleted()

    void delete(Serializable id)
}

4. Implementation Details

This section provides a brief overview of the internal implementation of the Grails Logical Delete plugin.

The LogicalDelete trait defines the deleted property and overrides core persistence methods to implement soft deletion and query filtering behavior.

The PreQueryListener listens for query events and modifies the query to automatically exclude logically deleted records.

The project’s Spock tests serve as a useful reference for examples and usage patterns of the plugin’s API.

5. API Documentation

API documentation may be found here.