| Summary: | LAZY IndirectSet is fetched needlessly during a Query | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Product: | z_Archived | Reporter: | Patric Rufflar <patric> | ||||||||||
| Component: | Eclipselink | Assignee: | Tom Ware <tom.ware> | ||||||||||
| Status: | CLOSED FIXED | QA Contact: | |||||||||||
| Severity: | critical | ||||||||||||
| Priority: | P3 | CC: | alfredo.osorio, eclipselink.orm-inbox, tom.ware | ||||||||||
| Version: | unspecified | ||||||||||||
| Target Milestone: | --- | ||||||||||||
| Hardware: | PC | ||||||||||||
| OS: | Windows XP | ||||||||||||
| Whiteboard: | |||||||||||||
| Attachments: |
|
||||||||||||
|
Description
Patric Rufflar
Created attachment 196573 [details]
patch which adds the testcase
Created attachment 196574 [details]
persistence.xml which contains the test persistence unit
Targetting for 2.2.1
This issue occurs because our check to see whether to refresh the objects in the M-1 relationship checks a blank ValueHolder that has just been built as part of buildObject. That valueholder appears to be instantiated because it has not been populated yet.
See ForeignReferenceMapping.buildCloneFromRow()
Object oldAttribute = this.getAttributeValueFromObject(clone);
setAttributeValueInObject(clone, clonedAttributeValue); // set this first to prevent infinite recursion
if (this.indirectionPolicy.objectIsInstantiatedOrChanged(oldAttribute)){
this.indirectionPolicy.instantiateObject(clone, clonedAttributeValue);
Here, for a 1-1 or M-1, oldAttribute will be a new instance of valueholder and EclipseLink will think it is instantiated therefore instantiating the object
This is a bigger deal for a M-1 than a 1-1 because triggering the M-1 will mean we have to deal with the 1-M relationship on the other side and trigger it.
Possible solution is to remove this block from ForeignReferenceMapping.buildCloneFromRow()
if (executionSession.isUnitOfWork() && sourceQuery.shouldRefreshIdentityMapResult()){
Object oldAttribute = this.getAttributeValueFromObject(clone);
setAttributeValueInObject(clone, clonedAttributeValue); // set this first to prevent infinite recursion
if (this.indirectionPolicy.objectIsInstantiatedOrChanged(oldAttribute)){
this.indirectionPolicy.instantiateObject(clone, clonedAttributeValue);
}
}
Cascade refresh tests will need to be added.
Alternate solution: See why oldAttribute has been added as an Empty ValueHolder and if it is weaving-related, we can check isNewlyWeavedValueHolder in WeavedBasicIndirectionPolicy.objectIsInstantiatedOrChanged() Created attachment 199839 [details]
proposed fix 2.2 stream
Created attachment 199842 [details]
proposed fix - trunk stream
The reason for the issue is a set of changes related to handling relationships with non-cacheable entities
Those changes included an update that would refresh across LAZY relationships in certain cases. The issue with the change was that while we were building the entity the relationship would hold a null, and we would treat it as though it needed refreshing.
The fix is to detect the null in the partially built object.
Fixed in 2.2.1, 2.3.1 and trunk Fix adds a way to as an IndirectionPolicy if it has finished building it's object and checks for fully built objects before refeshing Reviewed by Andrei Ilitchev Added test to CacheableJUnitTestModel Tested with JPA LRG and Core LRG Thanks, Tom. This patch made that when you have a query with QueryHint.REFRESH to true and you have an entity with a default list initialized example
public class Department {
}
This patch made that when you have a query with QueryHint.REFRESH to true and you have an entity with a default list initialized example
public class Department {
private List<Employee> employees = new ArrayList<Employee>();
@OneToMany(mappedBy="department", cascade=CascadeType.ALL)
public List<Employee> getEmployees() {
return this.employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
It fetches the OneToMany relationship even though it is LAZY. If you remove the new ArrayList<Employee>() it doesn't fetch the data. This has something to do with the way org.eclipse.persistence.mappings.ForeignReferenceMapping.buildCloneFromRow:
boolean wasAttributeValueFullyBuilt = isAttributeValueFullyBuilt(clone);
determines if the relationship was initialized, it wasn't it just have an empty list not created by EclipseLink.
Maybe it also has something to do with:
org.eclipse.persistence.internal.indirection.TransparentIndirectionPolicy.objectIsInstantiated(Object)
public boolean objectIsInstantiated(Object object) {
if (object instanceof IndirectContainer) {
return ((IndirectContainer)object).isInstantiated();
} else {
return true;// it must be a "real" collection
}
}
Maybe it shouldn't return true because it is an instance of ArrayList and it should return false in the else statement.
I'm interested in resolving this bug if you could give me an advice to solve it, I'll be happy to provide a patch. The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink |