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
>

Reply via email to