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
