Description
DataManager.getReference() returns an entity instance that carries nothing but the id. However, EntityStates.isLoaded() reports every attribute of such an instance as loaded, and EntityStates.isLoadedWithFetchPlan() reports that it conforms to any fetch plan.
As a result, code that uses these methods to find out whether an instance actually has data cannot tell a reference from a fully loaded entity, and treats the nulls as real values.
Steps to reproduce
Order order = dataManager.getReference(Order.class, someId);
entityStates.isLoaded(order, "number"); // local attribute
entityStates.isLoaded(order, "customer"); // to-one reference
entityStates.isLoaded(order, "orderLines"); // to-many collection
entityStates.isLoadedWithFetchPlan(order, FetchPlan.BASE);
Actual behavior
All the calls return true.
EntityStates.isLoadedSafe() returns UNKNOWN for every attribute, so it does not help either, and EntityStates.getCurrentFetchPlan() returns a fetch plan containing all attributes of the entity.
Expected behavior
An attribute that was never fetched is reported as not loaded, so a reference instance reports only its id as loaded:
isLoaded() returns false for all attributes except the id.
isLoadedSafe() returns NO for them, without invoking getters.
isLoadedWithFetchPlan() returns false for a fetch plan that contains anything besides the id.
getCurrentFetchPlan() returns a fetch plan with the id only.
Impact
EntitySerialization decides which attributes to write from isLoaded(), so a reference is serialized as a complete entity full of nulls. This affects REST, BPM process variables, and everything else built on entity serialization.
DataContext.merge() copies attributes from the source when isLoaded() is true for them, so merging a reference into a context that already holds the loaded instance overwrites real values with nulls.
- Application code that reloads an entity only when the required attributes are missing skips the reload and keeps working with nulls.
Note that saving a reference is not affected: it is a patch instance, so only non-null attributes are written to the database.
Reproduced on 3.1.999-SNAPSHOT with the EclipseLink data store.
Description
DataManager.getReference()returns an entity instance that carries nothing but the id. However,EntityStates.isLoaded()reports every attribute of such an instance as loaded, andEntityStates.isLoadedWithFetchPlan()reports that it conforms to any fetch plan.As a result, code that uses these methods to find out whether an instance actually has data cannot tell a reference from a fully loaded entity, and treats the nulls as real values.
Steps to reproduce
Actual behavior
All the calls return
true.EntityStates.isLoadedSafe()returnsUNKNOWNfor every attribute, so it does not help either, andEntityStates.getCurrentFetchPlan()returns a fetch plan containing all attributes of the entity.Expected behavior
An attribute that was never fetched is reported as not loaded, so a reference instance reports only its id as loaded:
isLoaded()returnsfalsefor all attributes except the id.isLoadedSafe()returnsNOfor them, without invoking getters.isLoadedWithFetchPlan()returnsfalsefor a fetch plan that contains anything besides the id.getCurrentFetchPlan()returns a fetch plan with the id only.Impact
EntitySerializationdecides which attributes to write fromisLoaded(), so a reference is serialized as a complete entity full of nulls. This affects REST, BPM process variables, and everything else built on entity serialization.DataContext.merge()copies attributes from the source whenisLoaded()is true for them, so merging a reference into a context that already holds the loaded instance overwrites real values with nulls.Note that saving a reference is not affected: it is a patch instance, so only non-null attributes are written to the database.
Reproduced on 3.1.999-SNAPSHOT with the EclipseLink data store.