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