Soft delete an entity in Grails with Hibernate Fil

2019-07-03 05:37发布

问题:

I was looking for a way to avoid deleting my users from DB, but instead to mark them as deleted and don't bring them back in queries.

I found this plugin http://grails.org/plugin/hibernate-filter, which was a great tool for the task.

But when I tried to implement my solution, I passed trought same problems whose solutions wheren't (or I was not able to find) on internet.

So, next, I describe the way that I solve the problem of soft delete.

回答1:

In this example I will make my class User to handle it's delete() method as a soft delete, setting the attribute lowDate with the actual date when delete() is called on an User instance. The idea is that users with lowDate != null will be ignored by GORM queries.

1) Intall Hibernate Filter Plugin. Look for the dependency at the plugin's page: http://grails.org/plugin/hibernate-filter. Take a look at the documentation.

2) Add to Datasource the following:

import org.grails.plugin.hibernate.filter.HibernateFilterDomainConfiguration

environments {
    development {
    dataSource {
        ...    
        configClass = HibernateFilterDomainConfiguration
    }
    }
    test {
    dataSource {
        ...
        configClass = HibernateFilterDomainConfiguration
    }
    }
    production {
    dataSource {
        ...
        configClass = HibernateFilterDomainConfiguration
    }
    }   
}

3) Define your filter at the class:

class User {
    ...
    String email
    Date lowDate
    static hibernateFilters = {
        deletedFilter(condition:'low_date is null', default:true)
    }
    static constraints = {
        ...
        lowDate nullable: true
    }
    ...
}

Note: look at the way I defined the condition. The value it receives is sql, so be careful to name the attribute as it is on the database instead of the name on the class.

This will make GORM methods to avoid bringing users that have lowDate different than null.

4) Define beforeDelele in a way to avoid phisical deletion:

class User {
    ...
    def beforeDelete() {
        SecUser.executeUpdate("update SecUser su set lowDate = :lowDate where email = :email",
                                [lowDate: new Date(), email: email])
        return false
    }
}

Note: I tried a simpler way to implement beforeDelete() that was

def beforeDelete() {
    this.lowDate = new Date()
    this.save()
    return false
}

But when save() was called inside beforeDelete, the save method called beforeDelete, and so on, generating an StackOverflow. I don't know why this happens.

5) Enable the filter on BootStrap:

class BootStrap {
    ...
    def init = { servletContext ->
        User.enableHibernateFilter('deletedFilter')
        environments {
            ...
        }
    }
...
}

That's all, it show now work. To test the functionallity, here's some sample spock tests:

Note: 'build' method is from build-test-data plugin.

class UserIntegrationSpec extends IntegrationSpec {

    def 'it should not find users marked as deleted'(){
        given: 'some users with lowDate and some withOut lowDate (=null)'
            User.build(firstName:'delUser1', lowDate: new Date())
            User.build(firstName:'user1')
            User.build(firstName:'delUser2', lowDate: new Date())
            User.build(firstName:'user2')
            def users = User.list()
        expect: 'it should only find the ones with lowDate == null'
            users.size() == 2
            users.every { it.firstName == 'user1' || it.firstName == 'user2' }      
    }

    def 'it should only delete users logically' (){
        given: 'a persisted user'
            def user = User.build(firstName: 'logiDelUser')
        when: 'user.delete() is called'
            user.delete(failOnError:true, flush:true)
            def deletedUser
            def users 
            User.withoutHibernateFilters(){
                users = User.list()
                deletedUser = User.find { firstName == 'logiDelUser' }
            }
        then: 'it should not delete the user from the DB, but set a low date instead'
            users.size() != 0
            deletedUser.lowDate != null
            deletedUser.firstName == 'logiDelUser' 
    }
}

Hope that that helps!



回答2:

Another way to make a soft delete for entities in GORM is using the following plugin:

http://grails.org/plugin/logical-delete

It uses the "deleted" flag to mark an entity as deleted and is not shown in the queries. This plugin uses the Hibernate filter plugin