1515 */
1616package org .springframework .data .jdbc .repository .query ;
1717
18- import java .util .Collections ;
18+ import lombok .Value ;
19+
20+ import java .util .ArrayList ;
1921import java .util .List ;
2022
2123import org .springframework .data .domain .Pageable ;
2628import org .springframework .data .relational .core .dialect .Dialect ;
2729import org .springframework .data .relational .core .dialect .RenderContextFactory ;
2830import org .springframework .data .relational .core .mapping .PersistentPropertyPathExtension ;
31+ import org .springframework .data .relational .core .mapping .RelationalMappingContext ;
2932import org .springframework .data .relational .core .mapping .RelationalPersistentEntity ;
3033import org .springframework .data .relational .core .mapping .RelationalPersistentProperty ;
3134import org .springframework .data .relational .core .query .Criteria ;
35+ import org .springframework .data .relational .core .sql .Column ;
3236import org .springframework .data .relational .core .sql .Expression ;
3337import org .springframework .data .relational .core .sql .Select ;
3438import org .springframework .data .relational .core .sql .SelectBuilder ;
35- import org .springframework .data .relational .core .sql .SqlIdentifier ;
39+ import org .springframework .data .relational .core .sql .StatementBuilder ;
3640import org .springframework .data .relational .core .sql .Table ;
3741import org .springframework .data .relational .core .sql .render .SqlRenderer ;
3842import org .springframework .data .relational .repository .query .RelationalEntityMetadata ;
4246import org .springframework .data .repository .query .parser .Part ;
4347import org .springframework .data .repository .query .parser .PartTree ;
4448import org .springframework .jdbc .core .namedparam .MapSqlParameterSource ;
49+ import org .springframework .lang .Nullable ;
4550import org .springframework .util .Assert ;
4651
4752/**
4853 * Implementation of {@link RelationalQueryCreator} that creates {@link ParametrizedQuery} from a {@link PartTree}.
4954 *
5055 * @author Mark Paluch
56+ * @author Jens Schauder
5157 * @since 2.0
5258 */
5359class JdbcQueryCreator extends RelationalQueryCreator <ParametrizedQuery > {
5460
61+ private final RelationalMappingContext context ;
5562 private final PartTree tree ;
5663 private final RelationalParameterAccessor accessor ;
5764 private final QueryMapper queryMapper ;
@@ -62,20 +69,22 @@ class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
6269 * Creates new instance of this class with the given {@link PartTree}, {@link JdbcConverter}, {@link Dialect},
6370 * {@link RelationalEntityMetadata} and {@link RelationalParameterAccessor}.
6471 *
72+ * @param context
6573 * @param tree part tree, must not be {@literal null}.
6674 * @param converter must not be {@literal null}.
6775 * @param dialect must not be {@literal null}.
6876 * @param entityMetadata relational entity metadata, must not be {@literal null}.
6977 * @param accessor parameter metadata provider, must not be {@literal null}.
7078 */
71- JdbcQueryCreator (PartTree tree , JdbcConverter converter , Dialect dialect , RelationalEntityMetadata <?> entityMetadata ,
72- RelationalParameterAccessor accessor ) {
79+ JdbcQueryCreator (RelationalMappingContext context , PartTree tree , JdbcConverter converter , Dialect dialect ,
80+ RelationalEntityMetadata <?> entityMetadata , RelationalParameterAccessor accessor ) {
7381 super (tree , accessor );
7482
7583 Assert .notNull (converter , "JdbcConverter must not be null" );
7684 Assert .notNull (dialect , "Dialect must not be null" );
7785 Assert .notNull (entityMetadata , "Relational entity metadata must not be null" );
7886
87+ this .context = context ;
7988 this .tree = tree ;
8089 this .accessor = accessor ;
8190
@@ -91,7 +100,7 @@ class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
91100 * @param tree
92101 * @param parameters
93102 */
94- public static void validate (PartTree tree , Parameters <?, ?> parameters ,
103+ static void validate (PartTree tree , Parameters <?, ?> parameters ,
95104 MappingContext <? extends RelationalPersistentEntity <?>, ? extends RelationalPersistentProperty > context ) {
96105
97106 RelationalQueryCreator .validate (tree , parameters );
@@ -141,53 +150,167 @@ private static void validateProperty(PersistentPropertyPathExtension path) {
141150 * @return instance of {@link ParametrizedQuery}
142151 */
143152 @ Override
144- protected ParametrizedQuery complete (Criteria criteria , Sort sort ) {
153+ protected ParametrizedQuery complete (@ Nullable Criteria criteria , Sort sort ) {
145154
146155 RelationalPersistentEntity <?> entity = entityMetadata .getTableEntity ();
147156 Table table = Table .create (entityMetadata .getTableName ());
148157 MapSqlParameterSource parameterSource = new MapSqlParameterSource ();
149158
150- List <? extends Expression > columns = table .columns (getSelectProjection ());
151- if (columns .isEmpty ()) {
152- columns = Collections .singletonList (table .asterisk ());
153- }
159+ SelectBuilder .SelectLimitOffset limitOffsetBuilder = createSelectClause (entity , table );
160+ SelectBuilder .SelectWhere whereBuilder = applyLimitAndOffset (limitOffsetBuilder );
161+ SelectBuilder .SelectOrdered selectOrderBuilder = applyCriteria (criteria , entity , table , parameterSource ,
162+ whereBuilder );
163+ selectOrderBuilder = applyOrderBy (sort , entity , table , selectOrderBuilder );
164+
165+ Select select = selectOrderBuilder .build ();
166+
167+ String sql = SqlRenderer .create (renderContextFactory .createRenderContext ()).render (select );
168+
169+ return new ParametrizedQuery (sql , parameterSource );
170+ }
171+
172+ private SelectBuilder .SelectOrdered applyOrderBy (Sort sort , RelationalPersistentEntity <?> entity , Table table ,
173+ SelectBuilder .SelectOrdered selectOrdered ) {
174+
175+ return sort .isSorted () ? //
176+ selectOrdered .orderBy (queryMapper .getMappedSort (table , sort , entity )) //
177+ : selectOrdered ;
178+ }
179+
180+ private SelectBuilder .SelectOrdered applyCriteria (@ Nullable Criteria criteria , RelationalPersistentEntity <?> entity ,
181+ Table table , MapSqlParameterSource parameterSource , SelectBuilder .SelectWhere whereBuilder ) {
182+
183+ return criteria != null //
184+ ? whereBuilder .where (queryMapper .getMappedObject (parameterSource , criteria , table , entity )) //
185+ : whereBuilder ;
186+ }
154187
155- SelectBuilder .SelectFromAndJoin builder = Select . builder (). select ( columns ). from ( table );
188+ private SelectBuilder .SelectWhere applyLimitAndOffset ( SelectBuilder . SelectLimitOffset limitOffsetBuilder ) {
156189
157190 if (tree .isExistsProjection ()) {
158- builder = builder .limit (1 );
191+ limitOffsetBuilder = limitOffsetBuilder .limit (1 );
159192 } else if (tree .isLimiting ()) {
160- builder = builder .limit (tree .getMaxResults ());
193+ limitOffsetBuilder = limitOffsetBuilder .limit (tree .getMaxResults ());
161194 }
162195
163196 Pageable pageable = accessor .getPageable ();
164197 if (pageable .isPaged ()) {
165- builder = builder .limit (pageable .getPageSize ()).offset (pageable .getOffset ());
198+ limitOffsetBuilder = limitOffsetBuilder .limit (pageable .getPageSize ()).offset (pageable .getOffset ());
166199 }
167200
168- if (criteria != null ) {
169- builder .where (queryMapper .getMappedObject (parameterSource , criteria , table , entity ));
201+ return (SelectBuilder .SelectWhere ) limitOffsetBuilder ;
202+ }
203+
204+ private SelectBuilder .SelectLimitOffset createSelectClause (RelationalPersistentEntity <?> entity , Table table ) {
205+
206+ SelectBuilder .SelectJoin builder ;
207+ if (tree .isExistsProjection ()) {
208+
209+ Column idColumn = table .column (entity .getIdColumn ());
210+ builder = Select .builder ().select (idColumn ).from (table );
211+ } else {
212+ builder = selectBuilder (table );
170213 }
171214
172- if (sort .isSorted ()) {
173- builder .orderBy (queryMapper .getMappedSort (table , sort , entity ));
215+ return (SelectBuilder .SelectLimitOffset ) builder ;
216+ }
217+
218+ private SelectBuilder .SelectJoin selectBuilder (Table table ) {
219+
220+ List <Expression > columnExpressions = new ArrayList <>();
221+ RelationalPersistentEntity <?> entity = entityMetadata .getTableEntity ();
222+ SqlContext sqlContext = new SqlContext (entity );
223+
224+ List <Join > joinTables = new ArrayList <>();
225+ for (PersistentPropertyPath <RelationalPersistentProperty > path : context
226+ .findPersistentPropertyPaths (entity .getType (), p -> true )) {
227+
228+ PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension (context , path );
229+
230+ // add a join if necessary
231+ Join join = getJoin (sqlContext , extPath );
232+ if (join != null ) {
233+ joinTables .add (join );
234+ }
235+
236+ Column column = getColumn (sqlContext , extPath );
237+ if (column != null ) {
238+ columnExpressions .add (column );
239+ }
174240 }
175241
176- Select select = builder .build ();
242+ SelectBuilder .SelectAndFrom selectBuilder = StatementBuilder .select (columnExpressions );
243+ SelectBuilder .SelectJoin baseSelect = selectBuilder .from (table );
177244
178- String sql = SqlRenderer .create (renderContextFactory .createRenderContext ()).render (select );
245+ for (Join join : joinTables ) {
246+ baseSelect = baseSelect .leftOuterJoin (join .joinTable ).on (join .joinColumn ).equals (join .parentId );
247+ }
179248
180- return new ParametrizedQuery ( sql , parameterSource ) ;
249+ return baseSelect ;
181250 }
182251
183- private SqlIdentifier [] getSelectProjection () {
252+ /**
253+ * Create a {@link Column} for {@link PersistentPropertyPathExtension}.
254+ *
255+ * @param sqlContext
256+ * @param path the path to the column in question.
257+ * @return the statement as a {@link String}. Guaranteed to be not {@literal null}.
258+ */
259+ @ Nullable
260+ private Column getColumn (SqlContext sqlContext , PersistentPropertyPathExtension path ) {
261+
262+ // an embedded itself doesn't give an column, its members will though.
263+ // if there is a collection or map on the path it won't get selected at all, but it will get loaded with a separate
264+ // select
265+ // only the parent path is considered in order to handle arrays that get stored as BINARY properly
266+ if (path .isEmbedded () || path .getParentPath ().isMultiValued ()) {
267+ return null ;
268+ }
184269
185- RelationalPersistentEntity <?> tableEntity = entityMetadata . getTableEntity ();
270+ if ( path . isEntity ()) {
186271
187- if (tree .isExistsProjection ()) {
188- return new SqlIdentifier [] { tableEntity .getIdColumn () };
272+ // Simple entities without id include there backreference as an synthetic id in order to distinguish null entities
273+ // from entities with only null values.
274+
275+ if (path .isQualified () //
276+ || path .isCollectionLike () //
277+ || path .hasIdProperty () //
278+ ) {
279+ return null ;
280+ }
281+
282+ return sqlContext .getReverseColumn (path );
283+ }
284+
285+ return sqlContext .getColumn (path );
286+ }
287+
288+ @ Nullable
289+ Join getJoin (SqlContext sqlContext , PersistentPropertyPathExtension path ) {
290+
291+ if (!path .isEntity () || path .isEmbedded () || path .isMultiValued ()) {
292+ return null ;
189293 }
190294
191- return new SqlIdentifier [0 ];
295+ Table currentTable = sqlContext .getTable (path );
296+
297+ PersistentPropertyPathExtension idDefiningParentPath = path .getIdDefiningParentPath ();
298+ Table parentTable = sqlContext .getTable (idDefiningParentPath );
299+
300+ return new Join ( //
301+ currentTable , //
302+ currentTable .column (path .getReverseColumnName ()), //
303+ parentTable .column (idDefiningParentPath .getIdColumnName ()) //
304+ );
305+ }
306+
307+ /**
308+ * Value object representing a {@code JOIN} association.
309+ */
310+ @ Value
311+ static private class Join {
312+ Table joinTable ;
313+ Column joinColumn ;
314+ Column parentId ;
192315 }
193316}
0 commit comments