Context:
In Kotlin, I have entity classes that are versioned, so there’s the general entity class “EntityA” and the entity “EntityAVersion”.
@Entity
@Table(name = "entity_a")
class EntityA(
//single instanceCount field
) : VersionedEntity<EntityA, EntityAVersion>()
as well the versioned entities
@Entity
@Table(name = "entity_a_version")
@JsonInclude(JsonInclude.Include.NON_NULL)
class EntityAVersion(
//Many columns unique to this entity A
) : EntityVersion<EntityAVersion, EntityA>()
Imagine that there are pairs of these for B, C…G as well.
The VersionedEntity
class, from which EntityA
inherits, joins EntityA
with EntityAVersion
@MappedSuperclass
abstract class VersionedEntity<Self : VersionedEntity<Self, V>, V : EntityVersion<V, Self>>(
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_version_id", referencedColumnName = "id")
var latest: V? = null,
//other stuff
) : BaseEntity()
The Problem:
The ORM works fine for all the basic CRUD stuff until trying to make a request using a Pageable that contains a Sort for one of fields in EntityA.latest.someField. As JPA, inside its QueryUtils
class, walks down the path of EntityA
to latest
to someField
, it establishes how the entities join together.
However, it incorrectly decides that the type of latest
is EntityAVersion, regardless of what it should be (B, C, D, etc.).
From inside QueryUtils
in JPA 2.6.10
private static boolean requiresOuterJoin(From<?, ?> from, PropertyPath property, boolean isForSelection,
boolean hasRequiredOuterJoin) {
String segment = property.getSegment(); //at this point, segment is "latest"
...
Bindable<?> propertyPathModel;
Bindable<?> model = from.getModel();
ManagedType<?> managedType = null;
if (model instanceof ManagedType) {
managedType = (ManagedType<?>) model;
} else if (model instanceof SingularAttribute
&& ((SingularAttribute<?, ?>) model).getType() instanceof ManagedType) {
managedType = (ManagedType<?>) ((SingularAttribute<?, ?>) model).getType();
}
if (managedType != null) {
//HERE IS THE PROBLEM: "getAttribute(segment)" only ever returns "EntityAVersion"
propertyPathModel = (Bindable<?>) managedType.getAttribute(segment);
} else {
propertyPathModel = from.get(segment).getModel();
}
...
}
in the last if
block, propertyPathModel = (Bindable<?>) managedType.getAttribute(segment);
is where it goes awry. Stepping into “getAttribute()” brings us into the AbstractManagedType
class
from Hibernate-core:5.6.15.Final:
public PersistentAttributeDescriptor<? super J, ?> getAttribute(String name) {
PersistentAttributeDescriptor<? super J, ?> attribute = (PersistentAttributeDescriptor)this.declaredAttributes.get(name);
if (attribute == null && this.getSuperType() != null) {
attribute = this.getSuperType().getAttribute(name);
}
this.checkNotNull("Attribute ", attribute, name);
return attribute;
}
The line this.getSuperType().getAttribute(name)
(where name is “latest”) seemingly hopelessly only ever returns a SingularAttributeImpl
of type EntityVersionA
, irrespective of the “path” beginning with a different entity, e.g. “EntityB.latest.someFieldUniqueToB”.
The consequence of it being the wrong type of Bindable is that it only ever left joins to EntityVersionA, which causes JPA to bungle the sorting (the field unique to EntityVersionB, for instance, would never be found) and a stacktrace about not finding that property is printed.
Is there something that should change about the ORM that can fix this?