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.isPropertyInitializedeven 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.isPropertyInitializedoverload that additionally acceptsSupplier<Object> propertyof 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.Hibernatethe same.