New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Logical deletes are not cascaded to child entities #2
Comments
Just as an update: I have forked this project into a private location and have updated the code to successfully cascade deletes. My current solution is not a great one, but it works. I disabled the execution of LogicalDeleteDomainClassEnhancer.enhance(). Then I created an AST transform to inject a beforeDelete() into all domain classes annotated with @LogicalDelete. See snippet below. My understanding of Grails transactions and sessions is also limited, so you may be able to make this more performant too. As you'll see in the code's TODO, this solution is limited because the user cannot define their own beforeDelete trigger. Technically they can but the behavior can get complicated if I allowed it since they can have beforeDelete() and beforeDelete(Map). I suggest using one of two other techniques discussed on the Grails docs page, such as PreDeleteEventListener: http://grails.org/doc/latest/guide/GORM.html#eventsAutoTimestamping class GLogicalDeleteASTTransformation {
static addGrailsTriggerToLogicallyDelete(ClassNode node, SourceUnit source) {
ensureNoExistingBeforeDeleteTriggers(node, source)
def methodBodyAst = new AstBuilder().buildFromCode {
executeUpdate("UPDATE ${this.getClass().name} SET deleted = 1 WHERE id = :id", [id: id])
// Also update the in-memory entity since we are using SQL for the update, otherwise it becomes dirty.
deleted = 1
return false
}
def method = new MethodNode("beforeDelete", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, [] as Parameter[], [] as ClassNode[], (Statement) methodBodyAst[0])
node.addMethod(method)
}
// TODO: Consider using Hibernate's PreDeleteEventListener or Grails's AbstractPersistenceEventListener to allow the client to use their own beforeDelete triggers. See: http://grails.org/doc/latest/guide/GORM.html
private static ensureNoExistingBeforeDeleteTriggers(ClassNode node, SourceUnit source) {
if (node.methods.any { it.name == "beforeDelete" }) {
source.addException(new RuntimeException("Logical Delete plugin: Your class '$node.name' cannot have its own beforeDelete() method defined"))
}
}
} |
I did it using Hibernate Events and commenting LogicalDeleteDomainClassEnhancer.enhance() method. See below. at resources.groovy:
class MyPreDeleteEventListener implements PreDeleteEventListener{
protected final Logger log = LoggerFactory.getLogger(getClass())
@Override
public boolean onPreDelete(PreDeleteEvent evt) {
if(mustBeEnhanced(evt.entity.class)){
try{
evt.entity.withNewSession { session ->
session.get(evt.entity.class, evt.entity.id).executeUpdate("UPDATE ${evt.entity.class.name} SET deleted = 1 WHERE id = :id", [id: evt.entity.id])
}
} catch(Exception e) {
log.error(e.getMessage(), e)
}
return true
}
return false;
}
private static boolean mustBeEnhanced(clazz){
LogicalDeleteDomainClass.isAssignableFrom(clazz)
}
} |
Thanks for the code @lmadeira. I gave it a try but there was a conflict with the Envers plugin that I'm using. My tests seem to indicate that only one "hibernateEventListeners" bean can be created and only that been is honored by Grails to easily create HibernateEventListeners. I tried changing Logical Delete to loadBefore and loadAfter 'envers' and I noticed that either Envers worked, or Logical Delete, but not both. Not knowing much about how to register my own Hibernate event listeners without using the "hibernateEventListeners" bean I looked through the web for help and found some hope: http://www.javacodegeeks.com/2012/10/stuff-i-learned-from-grails-consulting.html. However the solution described there didn't work either because for some reason sessionFactory.getEventListeners() was not a valid method/property of my session factory (maybe a conflict with one of the other plugins I use, not sure). So then I stumbled across a plugin which used a different facility to register its listeners: https://github.com/robertoschwald/grails-audit-logging-plugin/blob/master/grails-audit-logging-plugin/src/groovy/org/codehaus/groovy/grails/plugins/orm/auditable/AuditLogListener.groovy That lead me to finally read the Grails docs :) Now everything is working well: compatible with Envers, and my domain classes can have their own beforeDelete() methods defined. My code is below. I'm using a String type for the "deleted" property because I need to support UUIDs as well as Numbers. In the plugin's descriptor:
Then the new listener class itself:
Let me know if you can spot any issues with it. Thanks again to all contributors of this plugin. |
@vahidpaz Do you plan to pull request this plugin or provide a new one ? |
When a parent entity is logically deleted, its children (as defined by GORM's "belongsTo") are not visited at all.
As an alternative some might want to use the Hibenerate Filter plugin, along with the GORM beforeDelete() method: http://stackoverflow.com/questions/12467407/soft-delete-an-entity-in-grails-with-hibernate-filters-plugin
However I have had others problems with the Hibernate Filter plugin. I'm curious to know if Logical Delete is planning to support cascading. Thanks.
The text was updated successfully, but these errors were encountered: