Skip to content

Commit

Permalink
HHH-8627 Support EntityGraphs through JPA Query hints
Browse files Browse the repository at this point in the history
  • Loading branch information
brmeyer authored and sebersole committed Oct 30, 2013
1 parent 52d095b commit ca2d057
Show file tree
Hide file tree
Showing 25 changed files with 560 additions and 67 deletions.
Expand Up @@ -117,5 +117,17 @@ private QueryHints() {
* Accepts a {@link javax.persistence.LockModeType} or a {@link org.hibernate.LockMode}
*/
public static final String NATIVE_LOCKMODE = "org.hibernate.lockMode";

/**
* Hint providing an EntityGraph. With JPQL/HQL, the sole functionality is attribute nodes are treated as
* FetchType.EAGER. Laziness is not affected.
*/
public static final String FETCHGRAPH = "javax.persistence.fetchgraph";

/**
* Hint providing an EntityGraph. With JPQL/HQL, the sole functionality is attribute nodes are treated as
* FetchType.EAGER. Laziness is not affected.
*/
public static final String LOADGRAPH = "javax.persistence.loadgraph";

}
@@ -0,0 +1,137 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* JBoss, Home of Professional Open Source
* Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
* as indicated by the @authors tag. All rights reserved.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
package org.hibernate.engine.query.spi;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.AttributeNode;
import javax.persistence.EntityGraph;
import javax.persistence.Subgraph;

import org.hibernate.QueryException;
import org.hibernate.engine.internal.JoinSequence;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.hql.internal.ast.tree.FromClause;
import org.hibernate.hql.internal.ast.tree.FromElement;
import org.hibernate.hql.internal.ast.tree.FromElementFactory;
import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.sql.JoinType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;

/**
* Encapsulates a JPA EntityGraph provided through a JPQL query hint. Converts the fetches into a list of AST
* FromElements. The logic is kept here as much as possible in order to make it easy to remove this in the future,
* once our AST is improved and this "hack" is no longer needed.
*
* @author Brett Meyer
*/
public class EntityGraphQueryHint {

private final EntityGraph<?> originEntityGraph;

public EntityGraphQueryHint( EntityGraph<?> originEntityGraph ) {
this.originEntityGraph = originEntityGraph;
}

public List<FromElement> toFromElements(FromClause fromClause, HqlSqlWalker walker) {
// If a role already has an explicit fetch in the query, skip it in the graph.
Map<String, FromElement> explicitFetches = new HashMap<String, FromElement>();
Iterator iter = fromClause.getFromElements().iterator();
while ( iter.hasNext() ) {
final FromElement fromElement = ( FromElement ) iter.next();
if (fromElement.getRole() != null) {
explicitFetches.put( fromElement.getRole(), fromElement );
}
}

return getFromElements( originEntityGraph.getAttributeNodes(), fromClause.getFromElement(), fromClause,
walker, explicitFetches );
}

private List<FromElement> getFromElements(List attributeNodes, FromElement origin, FromClause fromClause,
HqlSqlWalker walker, Map<String, FromElement> explicitFetches) {
final List<FromElement> fromElements = new ArrayList<FromElement>();

for (Object obj : attributeNodes) {
final AttributeNode<?> attributeNode = (AttributeNode<?>) obj;

final String attributeName = attributeNode.getAttributeName();
final String className = origin.getClassName();
final String role = className + "." + attributeName;

final String classAlias = origin.getClassAlias();

final String originTableAlias = origin.getTableAlias();

Type propertyType = origin.getPropertyType( attributeName, attributeName );

try {
FromElement fromElement = null;
if (!explicitFetches.containsKey( role )) {
if ( propertyType.isEntityType() ) {
final EntityType entityType = (EntityType) propertyType;

final String[] columns = origin.toColumns( originTableAlias, attributeName, false );
final String tableAlias = walker.getAliasGenerator().createName(
entityType.getAssociatedEntityName() );

final FromElementFactory fromElementFactory = new FromElementFactory( fromClause, origin,
attributeName, classAlias, columns, false);
final JoinSequence joinSequence = walker.getSessionFactoryHelper().createJoinSequence(
false, entityType, tableAlias, JoinType.LEFT_OUTER_JOIN, columns );
fromElement = fromElementFactory.createEntityJoin( entityType.getAssociatedEntityName(), tableAlias,
joinSequence, true, walker.isInFrom(), entityType, role );
}
else if ( propertyType.isCollectionType() ) {
final String[] columns = origin.toColumns( originTableAlias, attributeName, false );

final FromElementFactory fromElementFactory = new FromElementFactory( fromClause, origin,
attributeName, classAlias, columns, false);
final QueryableCollection queryableCollection = walker.getSessionFactoryHelper()
.requireQueryableCollection( role );
fromElement = fromElementFactory.createCollection(
queryableCollection, role, JoinType.LEFT_OUTER_JOIN, true, false ) ;
}
}

if (fromElement != null) {
fromElements.add( fromElement );

// recurse into subgraphs
for (Subgraph<?> subgraph : attributeNode.getSubgraphs().values()) {
fromElements.addAll( getFromElements( subgraph.getAttributeNodes(), fromElement,
fromClause, walker, explicitFetches ) );
}
}
}
catch (Exception e) {
throw new QueryException( "Could not apply the EntityGraph to the Query!", e );
}
}

return fromElements;
}
}
Expand Up @@ -53,7 +53,7 @@ public FilterQueryPlan(
boolean shallow,
Map enabledFilters,
SessionFactoryImplementor factory) {
super( hql, collectionRole, shallow, enabledFilters, factory );
super( hql, collectionRole, shallow, enabledFilters, factory, null );
this.collectionRole = collectionRole;
}

Expand Down
Expand Up @@ -83,8 +83,14 @@ public class HQLQueryPlan implements Serializable {
* @param enabledFilters The enabled filters (we only keep the names)
* @param factory The factory
*/
public HQLQueryPlan(String hql, boolean shallow, Map<String,Filter> enabledFilters, SessionFactoryImplementor factory) {
this( hql, null, shallow, enabledFilters, factory );
public HQLQueryPlan(String hql, boolean shallow, Map<String,Filter> enabledFilters,
SessionFactoryImplementor factory) {
this( hql, null, shallow, enabledFilters, factory, null );
}

public HQLQueryPlan(String hql, boolean shallow, Map<String,Filter> enabledFilters,
SessionFactoryImplementor factory, EntityGraphQueryHint entityGraphQueryHint) {
this( hql, null, shallow, enabledFilters, factory, entityGraphQueryHint );
}

@SuppressWarnings("unchecked")
Expand All @@ -93,7 +99,8 @@ protected HQLQueryPlan(
String collectionRole,
boolean shallow,
Map<String,Filter> enabledFilters,
SessionFactoryImplementor factory) {
SessionFactoryImplementor factory,
EntityGraphQueryHint entityGraphQueryHint) {
this.sourceQuery = hql;
this.shallow = shallow;

Expand All @@ -115,7 +122,7 @@ protected HQLQueryPlan(
for ( int i=0; i<length; i++ ) {
if ( hasCollectionRole ) {
translators[i] = queryTranslatorFactory
.createQueryTranslator( hql, concreteQueryStrings[i], enabledFilters, factory );
.createQueryTranslator( hql, concreteQueryStrings[i], enabledFilters, factory, entityGraphQueryHint );
translators[i].compile( querySubstitutions, shallow );
}
else {
Expand Down
Expand Up @@ -31,20 +31,20 @@
import java.util.Map;
import java.util.StringTokenizer;

import org.jboss.logging.Logger;

import org.hibernate.HibernateException;
import org.hibernate.LockOptions;
import org.hibernate.QueryException;
import org.hibernate.ScrollMode;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.query.spi.HQLQueryPlan;
import org.hibernate.hql.internal.classic.ParserHelper;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.FilterImpl;
import org.hibernate.internal.util.EntityPrinter;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;

/**
* @author Gavin King
Expand Down Expand Up @@ -77,6 +77,8 @@ public final class QueryParameters {
private String processedSQL;
private Type[] processedPositionalParameterTypes;
private Object[] processedPositionalParameterValues;

private HQLQueryPlan queryPlan;

public QueryParameters() {
this( ArrayHelper.EMPTY_TYPE_ARRAY, ArrayHelper.EMPTY_OBJECT_ARRAY );
Expand Down Expand Up @@ -589,4 +591,12 @@ public QueryParameters createCopyUsing(RowSelection selection) {
copy.processedPositionalParameterValues = this.processedPositionalParameterValues;
return copy;
}

public HQLQueryPlan getQueryPlan() {
return queryPlan;
}

public void setQueryPlan(HQLQueryPlan queryPlan) {
this.queryPlan = queryPlan;
}
}
Expand Up @@ -25,13 +25,13 @@
package org.hibernate.hql.internal.ast;
import java.util.Map;

import org.jboss.logging.Logger;

import org.hibernate.engine.query.spi.EntityGraphQueryHint;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.spi.FilterTranslator;
import org.hibernate.hql.spi.QueryTranslator;
import org.hibernate.hql.spi.QueryTranslatorFactory;
import org.hibernate.internal.CoreMessageLogger;
import org.jboss.logging.Logger;

/**
* Generates translators which uses the Antlr-based parser to perform
Expand All @@ -55,8 +55,9 @@ public QueryTranslator createQueryTranslator(
String queryIdentifier,
String queryString,
Map filters,
SessionFactoryImplementor factory) {
return new QueryTranslatorImpl( queryIdentifier, queryString, filters, factory );
SessionFactoryImplementor factory,
EntityGraphQueryHint entityGraphQueryHint) {
return new QueryTranslatorImpl( queryIdentifier, queryString, filters, factory, entityGraphQueryHint );
}

/**
Expand Down
Expand Up @@ -180,7 +180,6 @@ public HqlSqlWalker(
this.printer = new ASTPrinter( SqlTokenTypes.class );
}


// handle trace logging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

private int traceDepth = 0;
Expand Down Expand Up @@ -651,6 +650,12 @@ protected void processQuery(AST select, AST query) throws SemanticException {

// Was there an explicit select expression?
boolean explicitSelect = select != null && select.getNumberOfChildren() > 0;

// Add in the EntityGraph attribute nodes.
if (queryTranslatorImpl.getEntityGraphQueryHint() != null) {
qn.getFromClause().getFromElements().addAll(
queryTranslatorImpl.getEntityGraphQueryHint().toFromElements( qn.getFromClause(), this ) );
}

if ( !explicitSelect ) {
// No explicit select expression; render the id and properties
Expand Down
Expand Up @@ -36,6 +36,7 @@
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.ScrollableResults;
import org.hibernate.engine.query.spi.EntityGraphQueryHint;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.engine.spi.SessionFactoryImplementor;
Expand Down Expand Up @@ -105,6 +106,8 @@ public class QueryTranslatorImpl implements FilterTranslator {

private ParameterTranslations paramTranslations;
private List<ParameterSpecification> collectedParameterSpecifications;

private EntityGraphQueryHint entityGraphQueryHint;


/**
Expand All @@ -127,6 +130,16 @@ public QueryTranslatorImpl(
this.enabledFilters = enabledFilters;
this.factory = factory;
}

public QueryTranslatorImpl(
String queryIdentifier,
String query,
Map enabledFilters,
SessionFactoryImplementor factory,
EntityGraphQueryHint entityGraphQueryHint) {
this( queryIdentifier, query, enabledFilters, factory );
this.entityGraphQueryHint = entityGraphQueryHint;
}

/**
* Compile a "normal" query. This method may be called multiple
Expand Down Expand Up @@ -612,4 +625,12 @@ private void handleDotStructure(AST dotStructureRoot) {
}
}
}

public EntityGraphQueryHint getEntityGraphQueryHint() {
return entityGraphQueryHint;
}

public void setEntityGraphQueryHint(EntityGraphQueryHint entityGraphQueryHint) {
this.entityGraphQueryHint = entityGraphQueryHint;
}
}
Expand Up @@ -472,6 +472,8 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b
if ( lhsFromElement == null ) {
throw new QueryException( "Unable to locate appropriate lhs" );
}

String role = lhsFromElement.getClassName() + "." + propertyName;

FromElementFactory factory = new FromElementFactory(
currentFromClause,
Expand All @@ -487,7 +489,8 @@ private void dereferenceEntityJoin(String classAlias, EntityType propertyType, b
joinSequence,
fetch,
getWalker().isInFrom(),
propertyType
propertyType,
role
);
}
else {
Expand Down
Expand Up @@ -369,6 +369,10 @@ public boolean isCollectionJoin() {
public void setRole(String role) {
this.role = role;
}

public String getRole() {
return role;
}

public void setQueryableCollection(QueryableCollection queryableCollection) {
elementType.setQueryableCollection( queryableCollection );
Expand Down

0 comments on commit ca2d057

Please sign in to comment.