Skip to content

Commit 2ca8f4f

Browse files
schaudermp911de
authored andcommitted
JdbcAggregateTemplate honors columns specified in query.
If no columns are given, all columns are selected by default. If columns are specified, only these are selected. Joins normally triggered by columns from 1:1 relationships are not implemented, and the corresponding columns don't get loaded and can't be specified in a query. Limiting columns is not supported for single query loading. Closes #1803 Original pull request: #1967
1 parent 1511e15 commit 2ca8f4f

File tree

4 files changed

+146
-24
lines changed

4 files changed

+146
-24
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
4040
import org.springframework.lang.Nullable;
4141
import org.springframework.util.Assert;
42+
import org.springframework.util.CollectionUtils;
4243

4344
/**
4445
* Generates SQL statements to be used by {@link SimpleJdbcRepository}
@@ -507,45 +508,85 @@ private String createFindAllSql() {
507508
}
508509

509510
private SelectBuilder.SelectWhere selectBuilder() {
510-
return selectBuilder(Collections.emptyList());
511+
return selectBuilder(Collections.emptyList(), Query.empty());
512+
}
513+
514+
private SelectBuilder.SelectWhere selectBuilder(Query query) {
515+
return selectBuilder(Collections.emptyList(), query);
511516
}
512517

513518
private SelectBuilder.SelectWhere selectBuilder(Collection<SqlIdentifier> keyColumns) {
519+
return selectBuilder(keyColumns, Query.empty());
520+
}
521+
522+
private SelectBuilder.SelectWhere selectBuilder(Collection<SqlIdentifier> keyColumns, Query query) {
514523

515524
Table table = getTable();
516525

517-
Set<Expression> columnExpressions = new LinkedHashSet<>();
526+
Projection projection = getProjection(keyColumns, query, table);
527+
SelectBuilder.SelectAndFrom selectBuilder = StatementBuilder.select(projection.columns());
528+
SelectBuilder.SelectJoin baseSelect = selectBuilder.from(table);
518529

519-
List<Join> joinTables = new ArrayList<>();
520-
for (PersistentPropertyPath<RelationalPersistentProperty> path : mappingContext
521-
.findPersistentPropertyPaths(entity.getType(), p -> true)) {
530+
for (Join join : projection.joins()) {
531+
baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId);
532+
}
522533

523-
AggregatePath extPath = mappingContext.getAggregatePath(path);
534+
return (SelectBuilder.SelectWhere) baseSelect;
535+
}
524536

525-
// add a join if necessary
526-
Join join = getJoin(extPath);
527-
if (join != null) {
528-
joinTables.add(join);
537+
private Projection getProjection(Collection<SqlIdentifier> keyColumns, Query query, Table table) {
538+
539+
Set<Expression> columns = new LinkedHashSet<>();
540+
Set<Join> joins = new LinkedHashSet<>();
541+
542+
if (!CollectionUtils.isEmpty(query.getColumns())) {
543+
for (SqlIdentifier columnName : query.getColumns()) {
544+
545+
String columnNameString = columnName.getReference();
546+
RelationalPersistentProperty property = entity.getPersistentProperty(columnNameString);
547+
if (property != null) {
548+
549+
AggregatePath aggregatePath = mappingContext.getAggregatePath(
550+
mappingContext.getPersistentPropertyPath(columnNameString, entity.getTypeInformation()));
551+
gatherColumn(aggregatePath, joins, columns);
552+
} else {
553+
columns.add(Column.create(columnName, table));
554+
}
529555
}
556+
} else {
557+
for (PersistentPropertyPath<RelationalPersistentProperty> path : mappingContext
558+
.findPersistentPropertyPaths(entity.getType(), p -> true)) {
559+
560+
AggregatePath aggregatePath = mappingContext.getAggregatePath(path);
530561

531-
Column column = getColumn(extPath);
532-
if (column != null) {
533-
columnExpressions.add(column);
562+
gatherColumn(aggregatePath, joins, columns);
534563
}
535564
}
536565

537566
for (SqlIdentifier keyColumn : keyColumns) {
538-
columnExpressions.add(table.column(keyColumn).as(keyColumn));
567+
columns.add(table.column(keyColumn).as(keyColumn));
539568
}
540569

541-
SelectBuilder.SelectAndFrom selectBuilder = StatementBuilder.select(columnExpressions);
542-
SelectBuilder.SelectJoin baseSelect = selectBuilder.from(table);
570+
return new Projection(columns, joins);
571+
}
543572

544-
for (Join join : joinTables) {
545-
baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId);
573+
private void gatherColumn(AggregatePath aggregatePath, Set<Join> joins, Set<Expression> columns) {
574+
575+
joins.addAll(getJoins(aggregatePath));
576+
577+
Column column = getColumn(aggregatePath);
578+
if (column != null) {
579+
columns.add(column);
546580
}
581+
}
547582

548-
return (SelectBuilder.SelectWhere) baseSelect;
583+
/**
584+
* Projection including its source joins.
585+
*
586+
* @param columns
587+
* @param joins
588+
*/
589+
record Projection(Set<Expression> columns, Set<Join> joins) {
549590
}
550591

551592
private SelectBuilder.SelectOrdered selectBuilder(Collection<SqlIdentifier> keyColumns, Sort sort,
@@ -611,9 +652,24 @@ Column getColumn(AggregatePath path) {
611652
return sqlContext.getColumn(path);
612653
}
613654

655+
List<Join> getJoins(AggregatePath path) {
656+
657+
List<Join> joins = new ArrayList<>();
658+
while (!path.isRoot()) {
659+
Join join = getJoin(path);
660+
if (join != null) {
661+
joins.add(join);
662+
}
663+
664+
path = path.getParentPath();
665+
}
666+
return joins;
667+
}
668+
614669
@Nullable
615670
Join getJoin(AggregatePath path) {
616671

672+
// TODO: This doesn't handle paths with length > 1 correctly
617673
if (!path.isEntity() || path.isEmbedded() || path.isMultiValued()) {
618674
return null;
619675
}
@@ -876,7 +932,7 @@ public String selectByQuery(Query query, MapSqlParameterSource parameterSource)
876932

877933
Assert.notNull(parameterSource, "parameterSource must not be null");
878934

879-
SelectBuilder.SelectWhere selectBuilder = selectBuilder();
935+
SelectBuilder.SelectWhere selectBuilder = selectBuilder(query);
880936

881937
Select select = applyQueryOnSelect(query, parameterSource, selectBuilder) //
882938
.build();

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/AbstractJdbcAggregateTemplateIntegrationTests.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.stream.IntStream;
3030
import java.util.stream.Stream;
3131

32+
import org.junit.jupiter.api.Disabled;
3233
import org.junit.jupiter.api.Test;
3334
import org.springframework.beans.factory.annotation.Autowired;
3435
import org.springframework.context.ApplicationContext;
@@ -237,7 +238,25 @@ void findAllByQuery() {
237238
Query query = Query.query(criteria);
238239
Iterable<SimpleListParent> reloadedById = template.findAll(query, SimpleListParent.class);
239240

240-
assertThat(reloadedById).extracting(e -> e.id, e -> e.content.size()).containsExactly(tuple(two.id, 2));
241+
assertThat(reloadedById) //
242+
.extracting(e -> e.id, e-> e.name, e -> e.content.size()) //
243+
.containsExactly(tuple(two.id, two.name, 2));
244+
}
245+
246+
@Test // GH-1803
247+
void findAllByQueryWithColumns() {
248+
249+
template.save(SimpleListParent.of("one", "one_1"));
250+
SimpleListParent two = template.save(SimpleListParent.of("two", "two_1", "two_2"));
251+
template.save(SimpleListParent.of("three", "three_1", "three_2", "three_3"));
252+
253+
CriteriaDefinition criteria = CriteriaDefinition.from(Criteria.where("id").is(two.id));
254+
Query query = Query.query(criteria).columns("id");
255+
Iterable<SimpleListParent> reloadedById = template.findAll(query, SimpleListParent.class);
256+
257+
assertThat(reloadedById) //
258+
.extracting(e -> e.id, e-> e.name, e -> e.content.size()) //
259+
.containsExactly(tuple(two.id, null, 2));
241260
}
242261

243262
@Test // GH-1601
@@ -2337,5 +2356,10 @@ static class JdbcAggregateTemplateIntegrationTests extends AbstractJdbcAggregate
23372356
static class JdbcAggregateTemplateSingleQueryLoadingIntegrationTests
23382357
extends AbstractJdbcAggregateTemplateIntegrationTests {
23392358

2359+
@Disabled
2360+
@Override
2361+
void findAllByQueryWithColumns() {
2362+
super.findAllByQueryWithColumns();
2363+
}
23402364
}
23412365
}

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,19 +351,59 @@ void findAllPagedAndSorted() {
351351
@Test // GH-1919
352352
void selectByQuery() {
353353

354-
Query query = Query.query(Criteria.where("id").is(23L));
354+
Query query = Query.query(Criteria.where("id").is(23L)).columns(new String[0]);
355355

356356
String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource());
357357

358358
assertThat(sql).contains( //
359359
"SELECT", //
360+
"dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name", //
360361
"FROM dummy_entity", //
361362
"LEFT OUTER JOIN referenced_entity ref ON ref.dummy_entity = dummy_entity.id1", //
362363
"LEFT OUTER JOIN second_level_referenced_entity ref_further ON ref_further.referenced_entity = ref.x_l1id", //
363364
"WHERE dummy_entity.id1 = :id1" //
364365
);
365366
}
366367

368+
@Test // GH-1803
369+
void selectByQueryWithColumnLimit() {
370+
371+
Query query = Query.empty().columns("id", "alpha", "beta", "gamma");
372+
373+
String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource());
374+
375+
assertThat(sql).contains( //
376+
"SELECT dummy_entity.id1 AS id1, dummy_entity.alpha, dummy_entity.beta, dummy_entity.gamma", //
377+
"FROM dummy_entity" //
378+
);
379+
}
380+
381+
@Test // GH-1803
382+
void selectingSetContentSelectsAllColumns() {
383+
384+
Query query = Query.empty().columns("elements.content");
385+
386+
String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource());
387+
388+
assertThat(sql).contains( //
389+
"SELECT dummy_entity.id1 AS id1, dummy_entity.x_name AS x_name"//
390+
);
391+
}
392+
393+
@Test // GH-1803
394+
void selectByQueryWithMappedColumnPathsRendersCorrectSelection() {
395+
396+
Query query = Query.empty().columns("ref.content");
397+
398+
String sql = sqlGenerator.selectByQuery(query, new MapSqlParameterSource());
399+
400+
assertThat(sql).contains( //
401+
"SELECT", //
402+
"ref.id1 AS id1, ref.content AS x_content", //
403+
"FROM dummy_entity", //
404+
"LEFT OUTER JOIN referenced_entity ref ON ref.dummy_entity = dummy_entity.id1");
405+
}
406+
367407
@Test // GH-1919
368408
void selectBySortedQuery() {
369409

@@ -381,7 +421,8 @@ void selectBySortedQuery() {
381421
"ORDER BY dummy_entity.id1 ASC" //
382422
);
383423
assertThat(sql).containsOnlyOnce("LEFT OUTER JOIN referenced_entity ref ON ref.dummy_entity = dummy_entity.id1");
384-
assertThat(sql).containsOnlyOnce("LEFT OUTER JOIN second_level_referenced_entity ref_further ON ref_further.referenced_entity = ref.x_l1id");
424+
assertThat(sql).containsOnlyOnce(
425+
"LEFT OUTER JOIN second_level_referenced_entity ref_further ON ref_further.referenced_entity = ref.x_l1id");
385426
}
386427

387428
@Test // DATAJDBC-131, DATAJDBC-111

spring-data-relational/src/main/java/org/springframework/data/relational/core/query/Query.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
*/
4242
public class Query {
4343

44+
private static final Query EMPTY = new Query(null);
4445
private static final int NO_LIMIT = -1;
4546

4647
private final @Nullable CriteriaDefinition criteria;
@@ -84,7 +85,7 @@ private Query(@Nullable CriteriaDefinition criteria, List<SqlIdentifier> columns
8485
* @return
8586
*/
8687
public static Query empty() {
87-
return new Query(null);
88+
return EMPTY;
8889
}
8990

9091
/**

0 commit comments

Comments
 (0)