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
> >
> 

Reply via email to