Validation of lazy association through PreUpdateEvent by Hibernate and JSR 303 integration might trigger additional fetches.
Full reproducer: GitHub - Kamii0909/test-case-template-hibernate-orm-6
Fail for 6.x up to latest.
Specifically, @Size(max = 3) @ElementCollection private List<Long> lazyCols
will be fetched. Is this intended? JPA states that: Attributes that have not been loaded must not be loaded
, so I would assume this is a bug. This behavior is not observed with bytecode enhancement, which is at least consistency issue.
This behavior is caused by Hibernate.isPropertyInitialized(Object entity, String propertyName)
quickly bailing out (return true) for non-bytecode enhanced entity, and HibernateTraversableResolver reliance on this method to skip validation:
public boolean isReachable(...) {
return Hibernate.isInitialized( traversableObject )
&& Hibernate.isPropertyInitialized( traversableObject, traversableProperty.getName() );
}
Turning on byte-code enhancement is a quick hack since bytecode enhanced entities are treated differently by isPropertyInitialized
and properly checked, but all bytecode enhancement options are deprecated in recent Hibernate versions, both runtime or through build tool integrations. Entity returned by getReferenceById
is a proper HibernateProxy in all configurations, so isPropertyInitialized
will work, but since you can’t update that, no validation will be triggered anyway.
A fix could be:
- Properly implementing
Hibernate.isPropertyInitialized
even for non-enhanced or proxied entities. This might prompt some reflection and could upset AoT environment, which is backward compatibility concern. And as a side effect, client can’t customize property access strategy. - Introduce additional
Hibernate.isPropertyInitialized
overload that additionally acceptsSupplier<Object> property
of some sort, which HibernateTraversableResolver could pass the actual uninitialized PersistentCollection to. This left the property access strategy (reflection/getter…) to the caller. I don’t think the current TraversableResolver interface even has enough information for HibernateTraversableResolver to provide such information. - Same as above, but let HibernateTraversableResolver additionally check for
Hibernate.isInitialized(getProperty(entity, "propertyName"))
. Still facing the same implementation issue as above, but can keep the wildly used public APIorg.hibernate.Hibernate
the same.