Skip to content

Metamodel loses JPA flag for properties of second-level mapped superclasses #5375

@glebfox

Description

@glebfox

Environment

Jmix: 3.0.999-SNAPSHOT

Description

After migrating to Jmix 3.0 (master / 3.0.0-M2), reading an audit attribute (createdBy, createdDate, lastModifiedBy, lastModifiedDate) on an entity loaded through DataManager throws IllegalStateException: Cannot get unfetched attribute [...] from detached object. In UI, this breaks opening any detail view for an existing entity: InstanceLoaderImpl.loadDataContextImpl.merge reads audit attributes and fails.

The root cause is in the metamodel: properties declared on a mapped superclass located two or more levels above the entity class lose their JPA flag — MetadataTools.isJpa(metaProperty) returns false for them. With the typical base-class hierarchy Customer extends StandardTenantEntity extends StandardEntity, all properties of StandardEntity (id, version, audit and soft-delete attributes) are affected, while tenant declared on StandardTenantEntity (one level up) and the entity's own properties remain correct.

Consequences chain:

  1. Default fetch plans (_local, _base) skip non-JPA properties (FetchPlanRepositoryImpl.isPersistent → false), so audit attributes are no longer selected — on 2.8 the same app had them in _local and no fetch group was set on instances (full row was loaded).
  2. FetchGroupManager explicitly re-adds the primary key and soft-delete properties to the EclipseLink fetch group, but nothing re-adds audit properties — the loaded instance gets EntityFetchGroup without them.
  3. EntityStates.isLoaded(entity, "createdBy") returns true (non-JPA properties are considered always loaded), so DataContextImpl.mergeState reads the value and EclipseLink throws on the detached instance.

All involved Jmix classes (DataContextImpl.mergeState, EntityStates, load checkers, FetchGroupManager, JmixEntityFetchGroup, FetchPlanRepositoryImpl) are identical between release_2_8 and master — the regression is in the metamodel content, not in the consumers.

Steps to reproduce

  1. Take an entity with a two-level mapped superclass hierarchy (e.g. Jmix Bookstore Customer extends StandardTenantEntity extends StandardEntity, audit attributes declared on StandardEntity).
  2. Load it: Customer c = dataManager.load(Customer.class).id(id).one(); (any fetch plan, including none).
  3. Read an audit attribute: c.getCreatedBy();

A pure-metamodel check reproduces it without touching the database:

MetaClass metaClass = metadata.getClass(Customer.class);
metadataTools.isJpa(metaClass.getProperty("createdBy")); // false on 3.0, must be true

Actual behavior

java.lang.IllegalStateException: Cannot get unfetched attribute [createdBy] from detached object io.jmix.bookstore.customer.Customer-... [detached].
    at org.eclipse.persistence.internal.queries.EntityFetchGroup.onUnfetchedAttribute(...)
    at io.jmix.eclipselink.impl.JmixEntityFetchGroup.onUnfetchedAttribute(...)
    ...
    at io.jmix.bookstore.entity.StandardEntity.getCreatedBy(...)
    at io.jmix.core.entity.BaseEntityEntry.getAttributeValue(...)
    at io.jmix.flowui.model.impl.DataContextImpl.mergeState(DataContextImpl.java:272)
    at io.jmix.flowui.model.impl.InstanceLoaderImpl.load(InstanceLoaderImpl.java:133)

Measured state on 3.0 for Customer:

  • metadataTools.isJpa(...): false for id, version, createdBy, createdDate, lastModifiedBy, lastModifiedDate, deletedBy, deletedDate (level-2 superclass); true for tenant (level-1) and own properties.
  • _local plan: [tenant, firstName, lastName, email].
  • Instance fetch group: EntityFetchGroup(){firstName, lastName, address, deletedDate, associatedRegion, orders, id, version, deletedBy, email, tenant} — audit attributes missing.
  • EntityStates.isLoaded(entity, "createdBy") = true while the actual read throws.

Same app on Jmix 2.8.0: _local contains all local properties including audit, instance fetch group is null (full row loaded), getCreatedBy() returns the value.

Expected behavior

Properties inherited from any depth of @MappedSuperclass hierarchy keep their JPA flag in the metamodel; audit attributes are loaded as in 2.8, and reading them on a DataManager-loaded instance returns the value instead of throwing.

Root cause hypothesis

The regression appears with the Dynamic Model change (#5272 / #5271, commit b17800bfe6 "Dynamic Model (changes in core)"), which introduced per-generation metadata session cloning (io.jmix.core.impl.metadata.MetadataSessionCloneSupport, MetadataSessionCloneMetaPropertyHandler). The runtime metamodel is a clone, and cloning appears to lose the store/JPA flag for properties inherited from mapped superclasses more than one level above the entity. MetaModelLoader itself is unchanged in this area.

A secondary observation: entities with soft delete appear unaffected for deletedBy/deletedDate only because FetchGroupManager explicitly re-adds soft-delete properties (and the PK) to every fetch group; audit properties have no such fallback. This explains why adding the SoftDelete trait masks the problem for those attributes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    in: datatriageIssue is waiting for triagetype: regressionRegress comparing to a previous version

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions