2222import static com .introproventures .graphql .jpa .query .support .GraphQLSupport .isLogicalArgument ;
2323import static com .introproventures .graphql .jpa .query .support .GraphQLSupport .isPageArgument ;
2424import static com .introproventures .graphql .jpa .query .support .GraphQLSupport .isWhereArgument ;
25+ import static com .introproventures .graphql .jpa .query .support .GraphQLSupport .selections ;
2526import static graphql .introspection .Introspection .SchemaMetaFieldDef ;
2627import static graphql .introspection .Introspection .TypeMetaFieldDef ;
2728import static graphql .introspection .Introspection .TypeNameMetaFieldDef ;
6061import graphql .schema .GraphQLScalarType ;
6162import graphql .schema .GraphQLSchema ;
6263import graphql .schema .GraphQLType ;
64+ import jakarta .persistence .EntityGraph ;
6365import jakarta .persistence .EntityManager ;
66+ import jakarta .persistence .Subgraph ;
6467import jakarta .persistence .TypedQuery ;
6568import jakarta .persistence .criteria .AbstractQuery ;
6669import jakarta .persistence .criteria .CriteriaBuilder ;
@@ -121,6 +124,7 @@ public final class GraphQLJpaQueryFactory {
121124 private static final String DESC = "DESC" ;
122125
123126 private static final Logger logger = LoggerFactory .getLogger (GraphQLJpaQueryFactory .class );
127+ public static final String JAKARTA_PERSISTENCE_FETCHGRAPH = "jakarta.persistence.fetchgraph" ;
124128 private static Function <Object , Object > unproxy ;
125129
126130 static {
@@ -260,15 +264,26 @@ protected Stream<Object> queryResultStream(DataFetchingEnvironment environment,
260264 keys .toArray ()
261265 );
262266
267+ // Let's create entity graph from selection
268+ var entityGraph = createEntityGraph (queryEnvironment );
269+
263270 // Let's execute query and get wrap result into stream
264- return getResultStream (query , fetchSize , isDistinct );
271+ return getResultStream (query , fetchSize , isDistinct , entityGraph );
265272 }
266273
267- protected <T > Stream <T > getResultStream (TypedQuery <T > query , int fetchSize , boolean isDistinct ) {
274+ protected <T > Stream <T > getResultStream (
275+ TypedQuery <T > query ,
276+ int fetchSize ,
277+ boolean isDistinct ,
278+ EntityGraph <?> entityGraph
279+ ) {
268280 // Let' try reduce overhead and disable all caching
269281 query .setHint (ORG_HIBERNATE_READ_ONLY , true );
270282 query .setHint (ORG_HIBERNATE_FETCH_SIZE , fetchSize );
271283 query .setHint (ORG_HIBERNATE_CACHEABLE , false );
284+ if (entityGraph != null ) {
285+ query .setHint (JAKARTA_PERSISTENCE_FETCHGRAPH , entityGraph );
286+ }
272287
273288 if (logger .isDebugEnabled ()) {
274289 logger .info ("\n GraphQL JPQL Fetch Query String:\n {}" , getJPQLQueryString (query ));
@@ -441,7 +456,9 @@ protected Map<Object, List<Object>> loadOneToMany(DataFetchingEnvironment enviro
441456
442457 TypedQuery <Object []> query = getBatchQuery (environment , field , isDefaultDistinct (), keys );
443458
444- List <Object []> resultList = getResultList (query );
459+ var entityGraph = createEntityGraph (environment );
460+
461+ List <Object []> resultList = getResultList (query , entityGraph );
445462
446463 if (logger .isTraceEnabled ()) {
447464 logger .trace (
@@ -477,7 +494,9 @@ protected Map<Object, Object> loadManyToOne(DataFetchingEnvironment environment,
477494
478495 TypedQuery <Object []> query = getBatchQuery (environment , field , isDefaultDistinct (), keys );
479496
480- List <Object []> resultList = getResultList (query );
497+ var entityGraph = createEntityGraph (environment );
498+
499+ List <Object []> resultList = getResultList (query , entityGraph );
481500
482501 Map <Object , Object > resultMap = new LinkedHashMap <>(resultList .size ());
483502
@@ -486,7 +505,7 @@ protected Map<Object, Object> loadManyToOne(DataFetchingEnvironment environment,
486505 return resultMap ;
487506 }
488507
489- protected <T > List <T > getResultList (TypedQuery <T > query ) {
508+ protected <T > List <T > getResultList (TypedQuery <T > query , EntityGraph <?> entityGraph ) {
490509 if (logger .isDebugEnabled ()) {
491510 logger .info ("\n GraphQL JPQL Batch Query String:\n {}" , getJPQLQueryString (query ));
492511 }
@@ -496,6 +515,10 @@ protected <T> List<T> getResultList(TypedQuery<T> query) {
496515 query .setHint (ORG_HIBERNATE_FETCH_SIZE , defaultFetchSize );
497516 query .setHint (ORG_HIBERNATE_CACHEABLE , false );
498517
518+ if (entityGraph != null ) {
519+ query .setHint (JAKARTA_PERSISTENCE_FETCHGRAPH , entityGraph );
520+ }
521+
499522 return query .getResultList ();
500523 }
501524
@@ -1693,8 +1716,10 @@ private EmbeddableType<?> computeEmbeddableType(GraphQLObjectType objectType) {
16931716 * @return resolved GraphQL object type or null if no output type is provided
16941717 */
16951718 private GraphQLObjectType getObjectType (DataFetchingEnvironment environment ) {
1696- GraphQLType outputType = environment .getFieldType ();
1719+ return getObjectType (environment .getFieldType ());
1720+ }
16971721
1722+ private GraphQLObjectType getObjectType (GraphQLType outputType ) {
16981723 if (outputType instanceof GraphQLList ) outputType = ((GraphQLList ) outputType ).getWrappedType ();
16991724
17001725 if (outputType instanceof GraphQLObjectType ) return (GraphQLObjectType ) outputType ;
@@ -1976,6 +2001,88 @@ private <T> T detach(T entity) {
19762001 return entity ;
19772002 }
19782003
2004+ EntityGraph <?> createEntityGraph (DataFetchingEnvironment environment ) {
2005+ Field root = environment .getMergedField ().getSingleField ();
2006+ GraphQLObjectType fieldType = getObjectType (environment );
2007+ EntityType <?> entityType = getEntityType (fieldType );
2008+
2009+ EntityGraph <?> entityGraph = entityManager .createEntityGraph (entityType .getJavaType ());
2010+
2011+ var entityDescriptor = EntityIntrospector .introspect (entityType );
2012+
2013+ selections (root )
2014+ .forEach (selectedField -> {
2015+ var propertyDescriptor = entityDescriptor .getPropertyDescriptor (selectedField .getName ());
2016+
2017+ propertyDescriptor
2018+ .flatMap (AttributePropertyDescriptor ::getAttribute )
2019+ .ifPresent (attribute -> {
2020+ if (
2021+ isManagedType (attribute ) && hasSelectionSet (selectedField ) && hasNoArguments (selectedField )
2022+ ) {
2023+ var attributeFieldDefinition = fieldType .getFieldDefinition (attribute .getName ());
2024+ entityGraph .addAttributeNodes (attribute .getName ());
2025+ addSubgraph (
2026+ selectedField ,
2027+ attributeFieldDefinition ,
2028+ entityGraph .addSubgraph (attribute .getName ())
2029+ );
2030+ } else if (isBasic (attribute )) {
2031+ entityGraph .addAttributeNodes (attribute .getName ());
2032+ }
2033+ });
2034+ });
2035+
2036+ return entityGraph ;
2037+ }
2038+
2039+ void addSubgraph (Field field , GraphQLFieldDefinition fieldDefinition , Subgraph <?> subgraph ) {
2040+ var fieldObjectType = getObjectType (fieldDefinition .getType ());
2041+ var fieldEntityType = getEntityType (fieldObjectType );
2042+ var fieldEntityDescriptor = EntityIntrospector .introspect (fieldEntityType );
2043+
2044+ selections (field )
2045+ .forEach (selectedField -> {
2046+ var propertyDescriptor = fieldEntityDescriptor .getPropertyDescriptor (selectedField .getName ());
2047+
2048+ propertyDescriptor
2049+ .flatMap (AttributePropertyDescriptor ::getAttribute )
2050+ .ifPresent (attribute -> {
2051+ var selectedName = selectedField .getName ();
2052+
2053+ if (
2054+ hasSelectionSet (selectedField ) && isManagedType (attribute ) && hasNoArguments (selectedField )
2055+ ) {
2056+ var selectedFieldDefinition = fieldObjectType .getFieldDefinition (selectedName );
2057+ subgraph .addAttributeNodes (selectedName );
2058+ addSubgraph (selectedField , selectedFieldDefinition , subgraph .addSubgraph (selectedName ));
2059+ } else if (isBasic (attribute )) {
2060+ subgraph .addAttributeNodes (selectedName );
2061+ }
2062+ });
2063+ });
2064+ }
2065+
2066+ static boolean isManagedType (Attribute <?, ?> attribute ) {
2067+ return (
2068+ attribute .getPersistentAttributeType () != Attribute .PersistentAttributeType .EMBEDDED &&
2069+ attribute .getPersistentAttributeType () != Attribute .PersistentAttributeType .BASIC &&
2070+ attribute .getPersistentAttributeType () != Attribute .PersistentAttributeType .ELEMENT_COLLECTION
2071+ );
2072+ }
2073+
2074+ static boolean isBasic (Attribute <?, ?> attribute ) {
2075+ return !isManagedType (attribute );
2076+ }
2077+
2078+ static boolean hasNoArguments (Field field ) {
2079+ return !hasArguments (field );
2080+ }
2081+
2082+ static boolean hasArguments (Field field ) {
2083+ return field .getArguments () != null && !field .getArguments ().isEmpty ();
2084+ }
2085+
19792086 /**
19802087 * Creates builder to build {@link GraphQLJpaQueryFactory}.
19812088 * @return created builder
0 commit comments