Search code examples
grailsgrails-ormsoft-delete

Trouble implementing soft delete in grails


I am trying to find proper way to implement soft delete for number of my entities. The best way I found so far is by using beforeDelete() callback.

So my class looks like this:

class Goal {

    String definition;
    Account account;
    boolean tmpl = false;
    String tmplName;

    Timestamp dateCreated
    Timestamp lastUpdated
    Timestamp deletedAt
    ...
    def beforeDelete() {
        if (deletedAt == null) {
            deletedAt = new Timestamp(System.currentTimeMillis())
            this.save()
        }

        return false
    }
}

Deletion in my case is executed through transactional service class, as follows:

def deleteTemplate(Goal tmpl) {
    if (tmpl.tmpl != true) {
        throw new ValidationException("Provided object is not template!")
    }

    //delete related perceptions
    for (perception in tmpl.perceptions) {
        perception.delete()
    }

    tmpl.delete()
}

All I am getting is an error:

    Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [triz.rrm.Perception#3]. Stacktrace follows:
Message: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [triz.rrm.Perception#3]
    Line | Method
->>   63 | beforeDelete       in triz.rrm.Goal
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    153 | call               in org.grails.datastore.gorm.support.EventTriggerCaller$MethodCaller
|     96 | call . . . . . . . in org.grails.datastore.gorm.support.EventTriggerCaller
|     47 | onApplicationEvent in org.grails.datastore.mapping.engine.event.AbstractPersistenceEventListener
|    138 | deleteTmpl . . . . in triz.rrm.RrmTemplateController
|    198 | doFilter           in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter . . . . . in grails.plugin.cache.web.filter.AbstractFilter
|     53 | doFilter           in grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter
|     49 | doFilter . . . . . in grails.plugin.springsecurity.web.authentication.RequestHolderAuthenticationFilter
|     82 | doFilter           in grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter
|   1142 | runWorker . . . .  in java.util.concurrent.ThreadPoolExecutor
|    617 | run                in java.util.concurrent.ThreadPoolExecutor$Worker
^    745 | run . . . . . . .  in java.lang.Thread

I've tried everything including wrapping it with new session and new transaction, nothing helps. What am I missing?

Thank you for your advice. Alex.

P.S. If I am marking the service as NotTransactional, no errors but nothing is getting updated.


Solution

  • I don't know if that helps, but maybe you could try using hql in your beforeDelete():

    def beforeDelete() {
        if (deletedAt == null) {
            Goal.executeUpdate('update Goal set deletedAt = ? where id = ?', [new Timestamp(System.currentTimeMillis()), id])
        }
    
        return false
    }
    

    I was trying to get around similar problem once, and the problem is that when you do update your way, it's not being done at once, but it goes to hibernate's session and it is persisted on flush and in the mean time such exceptions may occur on accessing the object. Hql saves it straight away