Did you try adding the annotation? Even though it's not a domain object? Also it sounds like you want a trait instead of a parent class if the parent is not going to be a domain.
On 2026/08/26 07:31:46 Gianluca Sartori wrote: > Nope, the parent is a plain abstract class as per doc. > > This is the first time we're using inheritance in a project, since I got > issues the first time I tryed it in 2016. > We are keeping it minimal, it's just a common class to handle auditing > fields. > > Anyway, calling makeDirty() before saving fixes the issue, but if this is > not intended I will open an issue for Grails Data Hibernate 7 > > > Gianluca Sartori > -- > https://dueuno.com > > > On Tue, 25 Aug 2026 at 18:15, James Daugherty via dev <[email protected]> > wrote: > > > Do you have `@DirtyCheck` on the parent? I think that's what causes > > this behavior. > > > > On Tue, Aug 25, 2026 at 6:49 AM Gianluca Sartori <[email protected]> > > wrote: > > > > > > I am not sure if this is a bug or the intended behaviour but I am quite > > > sure the documentation does not talk about it. @Walter can you please > > give > > > it a look? > > > > > > I've used AI to summarize the findings. > > > > > > > > > *GORM/Hibernate does not detect changes to inherited properties during > > > dirty checking.* > > > > > > The property is correctly persisted if another property of the entity is > > > modified at the same time, or if markDirty() is explicitly called. > > However, > > > changing only the inherited property does not result in an UPDATE being > > > issued. > > > Expected behavior > > > > > > Changing a persistent property should mark the entity as dirty and cause > > > the property to be persisted when calling: > > > > > > obj.save(flush: true) > > > > > > Actual behavior > > > > > > The entity is not considered dirty when only an inherited property is > > > changed. > > > > > > For example: > > > > > > TPaymentMethod obj = TPaymentMethod.get(id) > > > > > > obj.dateDisabled = LocalDateTime.now() > > > obj.usernameDisabled = 'TEST' > > > > > > println obj.isDirty() > > > println obj.isDirty('dateDisabled') > > > println obj.isDirty('usernameDisabled') > > > > > > obj.save(flush: true, failOnError: true) > > > > > > The output is: > > > > > > dirty=false > > > dirty date=false > > > dirty username=false > > > > > > No SQL UPDATE is generated and the values are not persisted. > > > > > > However, if another property declared directly on TPaymentMethod is > > changed: > > > > > > obj.description = obj.description + ' TEST' > > > obj.dateDisabled = LocalDateTime.now() > > > > > > obj.save(flush: true, failOnError: true) > > > > > > the entity is detected as dirty and both changes are persisted correctly. > > > > > > Explicitly marking the entity as dirty also works: > > > > > > obj.dateDisabled = LocalDateTime.now() > > > obj.usernameDisabled = 'TEST' > > > > > > obj.markDirty() > > > > > > obj.save(flush: true, failOnError: true) > > > > > > Minimal exampleBase class > > > > > > abstract class SystemManaged { > > > > > > Long id > > > Long version > > > > > > LocalDateTime dateCreated > > > LocalDateTime lastUpdated > > > LocalDateTime dateDeleted > > > LocalDateTime dateDisabled > > > > > > String usernameCreated > > > String usernameUpdated > > > String usernameDeleted > > > String usernameDisabled > > > > > > static constraints = { > > > id nullable: true > > > version nullable: true > > > > > > dateCreated nullable: true > > > lastUpdated nullable: true > > > dateDeleted nullable: true > > > dateDisabled nullable: true > > > > > > usernameCreated nullable: true > > > usernameUpdated nullable: true > > > usernameDeleted nullable: true > > > usernameDisabled nullable: true > > > } > > > } > > > > > > Domain class > > > > > > @GrailsCompileStatic > > > class TPaymentMethod extends SystemManaged > > > implements GormEntity, MultiTenant<TPaymentMethod> { > > > > > > String description > > > > > > static constraints = { > > > description nullable: false > > > } > > > } > > > > > > Reproduction > > > > > > @Transactional > > > void test(Long id) { > > > TPaymentMethod obj = TPaymentMethod.get(id) > > > > > > obj.dateDisabled = LocalDateTime.now() > > > obj.usernameDisabled = 'TEST' > > > > > > println "dirty=${obj.isDirty()}" > > > println "dirty dateDisabled=${obj.isDirty('dateDisabled')}" > > > println "dirty usernameDisabled=${obj.isDirty('usernameDisabled')}" > > > println "dirty properties=${obj.dirtyPropertyNames}" > > > > > > obj.save(flush: true, failOnError: true) > > > } > > > > > > Observed result: > > > > > > dirty=false > > > dirty dateDisabled=false > > > dirty usernameDisabled=false > > > dirty properties=[] > > > > > > No UPDATE is generated. > > > > > > But this works: > > > > > > obj.description = 'changed' > > > obj.dateDisabled = LocalDateTime.now() > > > > > > obj.save(flush: true, failOnError: true) > > > > > > And this also works: > > > > > > obj.dateDisabled = LocalDateTime.now() > > > obj.markDirty() > > > > > > obj.save(flush: true, failOnError: true) > > > > > > Gianluca Sartori > > > -- > > > https://dueuno.com > > >
