Guillaume Polet
Guillaume Polet

Reputation: 47617

eclipselink nested batch fetch

I have set up a very simple model with 3 entities:

I would like to batch fetch both Employee and Address from fetching the Department, so I have the following JPL:

select d from Department d

and I have added the following hint:

query.setHint(QueryHints.BATCH, "d.employees.addresses");

Yet I constantly get the following error:

Exception [EclipseLink-6089] (Eclipse Persistence Services - 2.4.2.v20130514-5956486): org.eclipse.persistence.exceptions.QueryException
Exception Description: The expression has not been initialized correctly.  Only a single ExpressionBuilder should be used for a query. 
For parallel expressions, the query class must be provided to the ExpressionBuilder constructor, and the query's ExpressionBuilder must 
always be on the left side of the expression. 
Expression: [
Base my.jpa.test.Department]
Query: ReadAllQuery(referenceClass=Department sql="SELECT ID, NAME FROM DEPARTMENT")
    at org.eclipse.persistence.exceptions.QueryException.noExpressionBuilderFound(QueryException.java:904)
    at org.eclipse.persistence.expressions.ExpressionBuilder.getDescriptor(ExpressionBuilder.java:195)
    at org.eclipse.persistence.internal.expressions.DataExpression.getContainingDescriptor(DataExpression.java:214)
    at org.eclipse.persistence.internal.expressions.DataExpression.getMapping(DataExpression.java:221)
    at org.eclipse.persistence.internal.expressions.QueryKeyExpression.getMapping(QueryKeyExpression.java:426)
    at org.eclipse.persistence.mappings.DatabaseMapping.extractNestedExpressions(DatabaseMapping.java:464)
    at org.eclipse.persistence.mappings.ForeignReferenceMapping.prepareNestedBatchQuery(ForeignReferenceMapping.java:882)
    at org.eclipse.persistence.mappings.ForeignReferenceMapping.batchedValueFromRow(ForeignReferenceMapping.java:220)
    at org.eclipse.persistence.mappings.ForeignReferenceMapping.valueFromRow(ForeignReferenceMapping.java:2046)
    at org.eclipse.persistence.mappings.ForeignReferenceMapping.buildCloneFromRow(ForeignReferenceMapping.java:289)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildAttributesIntoWorkingCopyClone(ObjectBuilder.java:1617)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildWorkingCopyCloneFromRow(ObjectBuilder.java:1769)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObjectInUnitOfWork(ObjectBuilder.java:672)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:609)
    at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:564)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.buildObject(ObjectLevelReadQuery.java:777)
    at org.eclipse.persistence.queries.ReadAllQuery.registerResultInUnitOfWork(ReadAllQuery.java:797)
    at org.eclipse.persistence.queries.ReadAllQuery.executeObjectLevelReadQuery(ReadAllQuery.java:434)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeDatabaseQuery(ObjectLevelReadQuery.java:1150)
    at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:852)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.execute(ObjectLevelReadQuery.java:1109)
    at org.eclipse.persistence.queries.ReadAllQuery.execute(ReadAllQuery.java:393)
    at org.eclipse.persistence.queries.ObjectLevelReadQuery.executeInUnitOfWork(ObjectLevelReadQuery.java:1197)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2879)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1607)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1589)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1554)
    at org.eclipse.persistence.internal.jpa.QueryImpl.executeReadQuery(QueryImpl.java:231)
    at org.eclipse.persistence.internal.jpa.QueryImpl.getResultList(QueryImpl.java:411)
    at my.jpa.test.Department.main(Department.java:124)

If I use directly the @BatchFetch annotation on the @OneToMany relationships, then the query works perfectly.

How could I fix this issue without having to use the annotation (since the annotations would force batch query on all my queries)?

If you need additional info, or complete code, I am happy to provide it.

Upvotes: 3

Views: 1676

Answers (2)

apetrelli
apetrelli

Reputation: 718

I resolved the problem by using a customizer and creating a class that extends OneToOneMapping (but I think it works for ManyToOneMapping too), by overriding the prepareNestedBatchQuery method.

Replace:

            if (expressionBuilder.getQueryClass() == null) {
                expressionBuilder.setSession(query.getSession().getRootSession(null));
                expressionBuilder.setQueryClass(query.getReferenceClass());
            }

with:

            if (expressionBuilder.getQueryClass() == null) {
                expressionBuilder.setSession(query.getSession().getRootSession(null));
                expressionBuilder.setQueryClass(query.getReferenceClass());
            } else if (expressionBuilder.getSession() == null) {
                expressionBuilder.setSession(query.getSession().getRootSession(null));
            }

Your DescriptorCustomizer has to call descriptor.setMappings with a new Vector of mappings.

Upvotes: 0

A3oN95
A3oN95

Reputation: 11

I struggled with the same problem recently, and unfortunately I couldn't solve it either. As a workaround, I defined the SELECT with a @NamedQuery annotation in the parent entity:

@Entity
@NamedQueries
(
    { 
        @NamedQuery(name = "Department.batchFetch", query = "Select d from Department d", 
        hints = 
        {
            (...)
            // works with JOIN/EXISTS too
            @QueryHint(name = QueryHints.BATCH_TYPE, value = "IN"),
            @QueryHint(name = QueryHints.BATCH, value = "d.employees.addresses")

        }), 
    }
)
public class Person 
{
    (...)
}

The method calling the NamedQuery will have:

Query query = em.createNamedQuery(queryName); // "Department.batchFetch"
List<Department> result = query.getResultList();

It's not exactly what I wanted (I can't customize the entities I want to load in runtime), but it beats having ALL queries use batch fetch with the @BatchQuery for me.

Upvotes: 1

Related Questions