From d80614b4d8b0b4763206d3c905f75b11db7d1b62 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Mon, 27 Apr 2020 09:36:39 +0200 Subject: [PATCH] DATAJDBC-318 - Fixed the generation of the select list. The select list must include columns for 1:1 relationships. The implementation is copied from SqlGenerator and will be unified in the near future. Original pull request: #209. --- .../repository/query/JdbcQueryCreator.java | 195 ++++++++++++++---- .../repository/query/PartTreeJdbcQuery.java | 14 +- .../jdbc/repository/query/SqlContext.java | 70 +++++++ .../support/JdbcQueryLookupStrategy.java | 2 +- ...EmbeddedWithReferenceIntegrationTests.java | 28 ++- .../query/PartTreeJdbcQueryUnitTests.java | 121 +++++------ .../core/sql/DefaultSelectBuilder.java | 3 +- 7 files changed, 314 insertions(+), 119 deletions(-) create mode 100644 spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/SqlContext.java diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java index d361dbbe..b127b52f 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java @@ -15,7 +15,9 @@ */ package org.springframework.data.jdbc.repository.query; -import java.util.Collections; +import lombok.Value; + +import java.util.ArrayList; import java.util.List; import org.springframework.data.domain.Pageable; @@ -26,13 +28,15 @@ import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.RenderContextFactory; import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.query.Criteria; +import org.springframework.data.relational.core.sql.Column; import org.springframework.data.relational.core.sql.Expression; import org.springframework.data.relational.core.sql.Select; import org.springframework.data.relational.core.sql.SelectBuilder; -import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.data.relational.core.sql.StatementBuilder; import org.springframework.data.relational.core.sql.Table; import org.springframework.data.relational.core.sql.render.SqlRenderer; import org.springframework.data.relational.repository.query.RelationalEntityMetadata; @@ -42,16 +46,19 @@ import org.springframework.data.repository.query.Parameters; import org.springframework.data.repository.query.parser.Part; import org.springframework.data.repository.query.parser.PartTree; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** * Implementation of {@link RelationalQueryCreator} that creates {@link ParametrizedQuery} from a {@link PartTree}. * * @author Mark Paluch + * @author Jens Schauder * @since 2.0 */ class JdbcQueryCreator extends RelationalQueryCreator { + private final RelationalMappingContext context; private final PartTree tree; private final RelationalParameterAccessor accessor; private final QueryMapper queryMapper; @@ -62,20 +69,22 @@ class JdbcQueryCreator extends RelationalQueryCreator { * Creates new instance of this class with the given {@link PartTree}, {@link JdbcConverter}, {@link Dialect}, * {@link RelationalEntityMetadata} and {@link RelationalParameterAccessor}. * + * @param context * @param tree part tree, must not be {@literal null}. * @param converter must not be {@literal null}. * @param dialect must not be {@literal null}. * @param entityMetadata relational entity metadata, must not be {@literal null}. * @param accessor parameter metadata provider, must not be {@literal null}. */ - JdbcQueryCreator(PartTree tree, JdbcConverter converter, Dialect dialect, RelationalEntityMetadata entityMetadata, - RelationalParameterAccessor accessor) { + JdbcQueryCreator(RelationalMappingContext context, PartTree tree, JdbcConverter converter, Dialect dialect, + RelationalEntityMetadata entityMetadata, RelationalParameterAccessor accessor) { super(tree, accessor); Assert.notNull(converter, "JdbcConverter must not be null"); Assert.notNull(dialect, "Dialect must not be null"); Assert.notNull(entityMetadata, "Relational entity metadata must not be null"); + this.context = context; this.tree = tree; this.accessor = accessor; @@ -91,7 +100,7 @@ class JdbcQueryCreator extends RelationalQueryCreator { * @param tree * @param parameters */ - public static void validate(PartTree tree, Parameters parameters, + static void validate(PartTree tree, Parameters parameters, MappingContext, ? extends RelationalPersistentProperty> context) { RelationalQueryCreator.validate(tree, parameters); @@ -141,53 +150,167 @@ class JdbcQueryCreator extends RelationalQueryCreator { * @return instance of {@link ParametrizedQuery} */ @Override - protected ParametrizedQuery complete(Criteria criteria, Sort sort) { + protected ParametrizedQuery complete(@Nullable Criteria criteria, Sort sort) { RelationalPersistentEntity entity = entityMetadata.getTableEntity(); Table table = Table.create(entityMetadata.getTableName()); MapSqlParameterSource parameterSource = new MapSqlParameterSource(); - List columns = table.columns(getSelectProjection()); - if (columns.isEmpty()) { - columns = Collections.singletonList(table.asterisk()); - } + SelectBuilder.SelectLimitOffset limitOffsetBuilder = createSelectClause(entity, table); + SelectBuilder.SelectWhere whereBuilder = applyLimitAndOffset(limitOffsetBuilder); + SelectBuilder.SelectOrdered selectOrderBuilder = applyCriteria(criteria, entity, table, parameterSource, + whereBuilder); + selectOrderBuilder = applyOrderBy(sort, entity, table, selectOrderBuilder); - SelectBuilder.SelectFromAndJoin builder = Select.builder().select(columns).from(table); - - if (tree.isExistsProjection()) { - builder = builder.limit(1); - } else if (tree.isLimiting()) { - builder = builder.limit(tree.getMaxResults()); - } - - Pageable pageable = accessor.getPageable(); - if (pageable.isPaged()) { - builder = builder.limit(pageable.getPageSize()).offset(pageable.getOffset()); - } - - if (criteria != null) { - builder.where(queryMapper.getMappedObject(parameterSource, criteria, table, entity)); - } - - if (sort.isSorted()) { - builder.orderBy(queryMapper.getMappedSort(table, sort, entity)); - } - - Select select = builder.build(); + Select select = selectOrderBuilder.build(); String sql = SqlRenderer.create(renderContextFactory.createRenderContext()).render(select); return new ParametrizedQuery(sql, parameterSource); } - private SqlIdentifier[] getSelectProjection() { + private SelectBuilder.SelectOrdered applyOrderBy(Sort sort, RelationalPersistentEntity entity, Table table, + SelectBuilder.SelectOrdered selectOrdered) { - RelationalPersistentEntity tableEntity = entityMetadata.getTableEntity(); + return sort.isSorted() ? // + selectOrdered.orderBy(queryMapper.getMappedSort(table, sort, entity)) // + : selectOrdered; + } + + private SelectBuilder.SelectOrdered applyCriteria(@Nullable Criteria criteria, RelationalPersistentEntity entity, + Table table, MapSqlParameterSource parameterSource, SelectBuilder.SelectWhere whereBuilder) { + + return criteria != null // + ? whereBuilder.where(queryMapper.getMappedObject(parameterSource, criteria, table, entity)) // + : whereBuilder; + } + + private SelectBuilder.SelectWhere applyLimitAndOffset(SelectBuilder.SelectLimitOffset limitOffsetBuilder) { if (tree.isExistsProjection()) { - return new SqlIdentifier[] { tableEntity.getIdColumn() }; + limitOffsetBuilder = limitOffsetBuilder.limit(1); + } else if (tree.isLimiting()) { + limitOffsetBuilder = limitOffsetBuilder.limit(tree.getMaxResults()); } - return new SqlIdentifier[0]; + Pageable pageable = accessor.getPageable(); + if (pageable.isPaged()) { + limitOffsetBuilder = limitOffsetBuilder.limit(pageable.getPageSize()).offset(pageable.getOffset()); + } + + return (SelectBuilder.SelectWhere) limitOffsetBuilder; + } + + private SelectBuilder.SelectLimitOffset createSelectClause(RelationalPersistentEntity entity, Table table) { + + SelectBuilder.SelectJoin builder; + if (tree.isExistsProjection()) { + + Column idColumn = table.column(entity.getIdColumn()); + builder = Select.builder().select(idColumn).from(table); + } else { + builder = selectBuilder(table); + } + + return (SelectBuilder.SelectLimitOffset) builder; + } + + private SelectBuilder.SelectJoin selectBuilder(Table table) { + + List columnExpressions = new ArrayList<>(); + RelationalPersistentEntity entity = entityMetadata.getTableEntity(); + SqlContext sqlContext = new SqlContext(entity); + + List joinTables = new ArrayList<>(); + for (PersistentPropertyPath path : context + .findPersistentPropertyPaths(entity.getType(), p -> true)) { + + PersistentPropertyPathExtension extPath = new PersistentPropertyPathExtension(context, path); + + // add a join if necessary + Join join = getJoin(sqlContext, extPath); + if (join != null) { + joinTables.add(join); + } + + Column column = getColumn(sqlContext, extPath); + if (column != null) { + columnExpressions.add(column); + } + } + + SelectBuilder.SelectAndFrom selectBuilder = StatementBuilder.select(columnExpressions); + SelectBuilder.SelectJoin baseSelect = selectBuilder.from(table); + + for (Join join : joinTables) { + baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId); + } + + return baseSelect; + } + + /** + * Create a {@link Column} for {@link PersistentPropertyPathExtension}. + * + * @param sqlContext + * @param path the path to the column in question. + * @return the statement as a {@link String}. Guaranteed to be not {@literal null}. + */ + @Nullable + private Column getColumn(SqlContext sqlContext, PersistentPropertyPathExtension path) { + + // an embedded itself doesn't give an column, its members will though. + // 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 + // select + // only the parent path is considered in order to handle arrays that get stored as BINARY properly + if (path.isEmbedded() || path.getParentPath().isMultiValued()) { + return null; + } + + if (path.isEntity()) { + + // Simple entities without id include there backreference as an synthetic id in order to distinguish null entities + // from entities with only null values. + + if (path.isQualified() // + || path.isCollectionLike() // + || path.hasIdProperty() // + ) { + return null; + } + + return sqlContext.getReverseColumn(path); + } + + return sqlContext.getColumn(path); + } + + @Nullable + Join getJoin(SqlContext sqlContext, PersistentPropertyPathExtension path) { + + if (!path.isEntity() || path.isEmbedded() || path.isMultiValued()) { + return null; + } + + Table currentTable = sqlContext.getTable(path); + + PersistentPropertyPathExtension idDefiningParentPath = path.getIdDefiningParentPath(); + Table parentTable = sqlContext.getTable(idDefiningParentPath); + + return new Join( // + currentTable, // + currentTable.column(path.getReverseColumnName()), // + parentTable.column(idDefiningParentPath.getIdColumnName()) // + ); + } + + /** + * Value object representing a {@code JOIN} association. + */ + @Value + static private class Join { + Table joinTable; + Column joinColumn; + Column parentId; } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java index cc9ed78b..27fa0981 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java @@ -18,6 +18,7 @@ package org.springframework.data.jdbc.repository.query; import org.springframework.data.domain.Sort; import org.springframework.data.jdbc.core.convert.JdbcConverter; import org.springframework.data.relational.core.dialect.Dialect; +import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.repository.query.RelationalEntityMetadata; import org.springframework.data.relational.repository.query.RelationalParameterAccessor; import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor; @@ -31,10 +32,12 @@ import org.springframework.util.Assert; * An {@link AbstractJdbcQuery} implementation based on a {@link PartTree}. * * @author Mark Paluch + * @author Jens Schauder * @since 2.0 */ public class PartTreeJdbcQuery extends AbstractJdbcQuery { + private final RelationalMappingContext context; private final Parameters parameters; private final Dialect dialect; private final JdbcConverter converter; @@ -43,22 +46,25 @@ public class PartTreeJdbcQuery extends AbstractJdbcQuery { /** * Creates a new {@link PartTreeJdbcQuery}. - * + * + * @param context must not be {@literal null}. * @param queryMethod must not be {@literal null}. * @param dialect must not be {@literal null}. * @param converter must not be {@literal null}. * @param operations must not be {@literal null}. * @param rowMapper must not be {@literal null}. */ - public PartTreeJdbcQuery(JdbcQueryMethod queryMethod, Dialect dialect, JdbcConverter converter, - NamedParameterJdbcOperations operations, RowMapper rowMapper) { + public PartTreeJdbcQuery(RelationalMappingContext context, JdbcQueryMethod queryMethod, Dialect dialect, JdbcConverter converter, + NamedParameterJdbcOperations operations, RowMapper rowMapper) { super(queryMethod, operations, rowMapper); + Assert.notNull(context, "RelationalMappingContext must not be null"); Assert.notNull(queryMethod, "JdbcQueryMethod must not be null"); Assert.notNull(dialect, "Dialect must not be null"); Assert.notNull(converter, "JdbcConverter must not be null"); + this.context = context; this.parameters = queryMethod.getParameters(); this.dialect = dialect; this.converter = converter; @@ -90,7 +96,7 @@ public class PartTreeJdbcQuery extends AbstractJdbcQuery { protected ParametrizedQuery createQuery(RelationalParametersParameterAccessor accessor) { RelationalEntityMetadata entityMetadata = getQueryMethod().getEntityInformation(); - JdbcQueryCreator queryCreator = new JdbcQueryCreator(tree, converter, dialect, entityMetadata, accessor); + JdbcQueryCreator queryCreator = new JdbcQueryCreator(context, tree, converter, dialect, entityMetadata, accessor); return queryCreator.createQuery(getDynamicSort(accessor)); } } diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/SqlContext.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/SqlContext.java new file mode 100644 index 00000000..0492c48a --- /dev/null +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/SqlContext.java @@ -0,0 +1,70 @@ +/* + * Copyright 2019-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jdbc.repository.query; + +import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension; +import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; +import org.springframework.data.relational.core.sql.Column; +import org.springframework.data.relational.core.sql.SqlIdentifier; +import org.springframework.data.relational.core.sql.Table; + +/** + * Utility to get from path to SQL DSL elements. This is a temporary class and duplicates + * {@link org.springframework.data.jdbc.core.convert.SqlContext}. + * + * @author Jens Schauder + * @author Mark Paluch + * @author Tyler Van Gorder + * @since 2.0 + */ +class SqlContext { + + private final RelationalPersistentEntity entity; + private final Table table; + + SqlContext(RelationalPersistentEntity entity) { + + this.entity = entity; + this.table = Table.create(entity.getTableName()); + } + + Column getIdColumn() { + return table.column(entity.getIdColumn()); + } + + Column getVersionColumn() { + return table.column(entity.getRequiredVersionProperty().getColumnName()); + } + + Table getTable() { + return table; + } + + Table getTable(PersistentPropertyPathExtension path) { + + SqlIdentifier tableAlias = path.getTableAlias(); + Table table = Table.create(path.getTableName()); + return tableAlias == null ? table : table.as(tableAlias); + } + + Column getColumn(PersistentPropertyPathExtension path) { + return getTable(path).column(path.getColumnName()).as(path.getColumnAlias()); + } + + Column getReverseColumn(PersistentPropertyPathExtension path) { + return getTable(path).column(path.getReverseColumnName()).as(path.getReverseColumnNameAlias()); + } +} diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java index 1c95e5d3..6c8ac595 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java @@ -101,7 +101,7 @@ class JdbcQueryLookupStrategy implements QueryLookupStrategy { RowMapper mapper = queryMethod.isModifyingQuery() ? null : createMapper(queryMethod); return new StringBasedJdbcQuery(queryMethod, operations, mapper, converter); } else { - return new PartTreeJdbcQuery(queryMethod, dialect, converter, operations, createMapper(queryMethod)); + return new PartTreeJdbcQuery(context, queryMethod, dialect, converter, operations, createMapper(queryMethod)); } } catch (Exception e) { throw QueryCreationException.create(queryMethod, e.getMessage()); diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java index 5e2b19b0..db3c0059 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryEmbeddedWithReferenceIntegrationTests.java @@ -20,7 +20,7 @@ import static org.assertj.core.api.Assertions.*; import lombok.Data; -import java.sql.SQLException; +import java.util.List; import org.junit.ClassRule; import org.junit.Rule; @@ -50,6 +50,7 @@ import org.springframework.transaction.annotation.Transactional; * Very simple use cases for creation and usage of JdbcRepositories with test {@link Embedded} annotation in Entities. * * @author Bastian Wilhelm + * @author Jens Schauder */ @ContextConfiguration @Transactional @@ -225,7 +226,28 @@ public class JdbcRepositoryEmbeddedWithReferenceIntegrationTests { assertThat(repository.findAll()).isEmpty(); } + @Test // DATAJDBC-318 + public void queryDerivationLoadsReferencedEntitiesCorrectly() { + + repository.save(createDummyEntity()); + repository.save(createDummyEntity()); + DummyEntity saved = repository.save(createDummyEntity()); + + assertThat(repository.findByTest(saved.test)) // + .extracting( // + e -> e.test, // + e -> e.embeddable.test, // + e -> e.embeddable.dummyEntity2.test // + ).containsExactly( // + tuple(saved.test, saved.embeddable.test, saved.embeddable.dummyEntity2.test), // + tuple(saved.test, saved.embeddable.test, saved.embeddable.dummyEntity2.test), // + tuple(saved.test, saved.embeddable.test, saved.embeddable.dummyEntity2.test) // + ); + + } + private static DummyEntity createDummyEntity() { + DummyEntity entity = new DummyEntity(); entity.setTest("root"); @@ -242,7 +264,9 @@ public class JdbcRepositoryEmbeddedWithReferenceIntegrationTests { return entity; } - interface DummyEntityRepository extends CrudRepository {} + interface DummyEntityRepository extends CrudRepository { + List findByTest(String test); + } @Data private static class DummyEntity { diff --git a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java index bdd3849f..8a7bf835 100644 --- a/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java +++ b/spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQueryUnitTests.java @@ -30,7 +30,6 @@ import java.util.Properties; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; - import org.springframework.data.annotation.Id; import org.springframework.data.jdbc.core.convert.BasicJdbcConverter; import org.springframework.data.jdbc.core.convert.JdbcConverter; @@ -61,7 +60,9 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; public class PartTreeJdbcQueryUnitTests { private static final String TABLE = "\"users\""; - private static final String ALL_FIELDS = "\"users\".*"; + private static final String ALL_FIELDS = "\"users\".\"ID\" AS \"ID\", \"users\".\"AGE\" AS \"AGE\", \"hated\".\"USER\" AS \"HATED_USER\", \"users\".\"ACTIVE\" AS \"ACTIVE\", \"users\".\"LAST_NAME\" AS \"LAST_NAME\", \"users\".\"FIRST_NAME\" AS \"FIRST_NAME\", \"users\".\"DATE_OF_BIRTH\" AS \"DATE_OF_BIRTH\", \"users\".\"HOBBY_REFERENCE\" AS \"HOBBY_REFERENCE\", \"hated\".\"NAME\" AS \"HATED_NAME\", \"users\".\"USER_CITY\" AS \"USER_CITY\", \"users\".\"USER_STREET\" AS \"USER_STREET\""; + private static final String JOIN_CLAUSE = "FROM \"users\" LEFT OUTER JOIN \"HOBBY\" AS \"hated\" ON \"hated\".\"USER\" = \"users\".\"ID\""; + private static final String BASE_SELECT = "SELECT " + ALL_FIELDS + " " + JOIN_CLAUSE; JdbcMappingContext mappingContext = new JdbcMappingContext(); JdbcConverter converter = new BasicJdbcConverter(mappingContext, mock(RelationResolver.class)); @@ -108,8 +109,7 @@ public class PartTreeJdbcQueryUnitTests { PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod); ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" })); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name"); } @Test // DATAJDBC-318 @@ -119,8 +119,7 @@ public class PartTreeJdbcQueryUnitTests { PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod); ParametrizedQuery query = jdbcQuery.createQuery((getAccessor(queryMethod, new Object[] { null }))); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" IS NULL"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" IS NULL"); } @Test // DATAJDBC-318 @@ -141,8 +140,8 @@ public class PartTreeJdbcQueryUnitTests { PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod); ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "Doe", "John" })); - assertThat(query.getQuery()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE - + ".\"LAST_NAME\" = :last_name AND (" + TABLE + ".\"FIRST_NAME\" = :first_name)"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"LAST_NAME\" = :last_name AND (" + TABLE + + ".\"FIRST_NAME\" = :first_name)"); } @Test // DATAJDBC-318 @@ -152,8 +151,8 @@ public class PartTreeJdbcQueryUnitTests { PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod); ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "Doe", "John" })); - assertThat(query.getQuery()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE - + ".\"LAST_NAME\" = :last_name OR (" + TABLE + ".\"FIRST_NAME\" = :first_name)"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"LAST_NAME\" = :last_name OR (" + TABLE + + ".\"FIRST_NAME\" = :first_name)"); } @Test // DATAJDBC-318 @@ -166,8 +165,8 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { from, to }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()).isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE - + ".\"DATE_OF_BIRTH\" BETWEEN :date_of_birth AND :date_of_birth1"); + assertThat(query.getQuery()) + .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"DATE_OF_BIRTH\" BETWEEN :date_of_birth AND :date_of_birth1"); assertThat(query.getParameterSource().getValue("date_of_birth")).isEqualTo(from); assertThat(query.getParameterSource().getValue("date_of_birth1")).isEqualTo(to); @@ -181,8 +180,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"AGE\" < :age"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" < :age"); } @Test // DATAJDBC-318 @@ -193,8 +191,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"AGE\" <= :age"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" <= :age"); } @Test // DATAJDBC-318 @@ -205,8 +202,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"AGE\" > :age"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" > :age"); } @Test // DATAJDBC-318 @@ -217,8 +213,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 30 }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"AGE\" >= :age"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" >= :age"); } @Test // DATAJDBC-318 @@ -229,8 +224,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()).isEqualTo( - "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"DATE_OF_BIRTH\" > :date_of_birth"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"DATE_OF_BIRTH\" > :date_of_birth"); } @Test // DATAJDBC-318 @@ -240,8 +234,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { new Date() }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()).isEqualTo( - "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"DATE_OF_BIRTH\" < :date_of_birth"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"DATE_OF_BIRTH\" < :date_of_birth"); } @Test // DATAJDBC-318 @@ -252,8 +245,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"AGE\" IS NULL"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" IS NULL"); } @Test // DATAJDBC-318 @@ -264,8 +256,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"AGE\" IS NOT NULL"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" IS NOT NULL"); } @Test // DATAJDBC-318 @@ -276,8 +267,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); } @Test // DATAJDBC-318 @@ -288,8 +278,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "%John%" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()).isEqualTo( - "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" NOT LIKE :first_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" NOT LIKE :first_name"); } @Test // DATAJDBC-318 @@ -300,8 +289,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); } @Test // DATAJDBC-318 @@ -312,8 +300,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Jo" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("Jo%"); } @@ -325,8 +312,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); } @Test // DATAJDBC-318 @@ -337,8 +323,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "hn" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("%hn"); } @@ -350,8 +335,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); } @Test // DATAJDBC-318 @@ -362,8 +346,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" LIKE :first_name"); assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("%oh%"); } @@ -375,8 +358,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()).isEqualTo( - "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" NOT LIKE :first_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" NOT LIKE :first_name"); } @Test // DATAJDBC-318 @@ -387,8 +369,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "oh" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()).isEqualTo( - "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"FIRST_NAME\" NOT LIKE :first_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" NOT LIKE :first_name"); assertThat(query.getParameterSource().getValue("first_name")).isEqualTo("%oh%"); } @@ -400,8 +381,8 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 123 }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()).isEqualTo( - "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"AGE\" = :age ORDER BY \"LAST_NAME\" DESC"); + assertThat(query.getQuery()) + .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" = :age ORDER BY \"LAST_NAME\" DESC"); } @Test // DATAJDBC-318 @@ -411,8 +392,8 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { 123 }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()).isEqualTo( - "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"AGE\" = :age ORDER BY \"LAST_NAME\" ASC"); + assertThat(query.getQuery()) + .isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" = :age ORDER BY \"LAST_NAME\" ASC"); } @Test // DATAJDBC-318 @@ -422,8 +403,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Doe" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"LAST_NAME\" != :last_name"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"LAST_NAME\" != :last_name"); } @Test // DATAJDBC-318 @@ -435,8 +415,7 @@ public class PartTreeJdbcQueryUnitTests { new Object[] { Collections.singleton(25) }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"AGE\" IN (:age)"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" IN (:age)"); } @Test // DATAJDBC-318 @@ -447,8 +426,7 @@ public class PartTreeJdbcQueryUnitTests { new Object[] { Collections.singleton(25) }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"AGE\" NOT IN (:age)"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"AGE\" NOT IN (:age)"); } @Test // DATAJDBC-318 @@ -459,8 +437,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"ACTIVE\" = TRUE"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"ACTIVE\" = TRUE"); } @Test // DATAJDBC-318 @@ -471,8 +448,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[0]); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()) - .isEqualTo("SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".\"ACTIVE\" = FALSE"); + assertThat(query.getQuery()).isEqualTo(BASE_SELECT + " WHERE " + TABLE + ".\"ACTIVE\" = FALSE"); } @Test // DATAJDBC-318 @@ -483,8 +459,8 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - assertThat(query.getQuery()).isEqualTo( - "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE UPPER(" + TABLE + ".\"FIRST_NAME\") = UPPER(:first_name)"); + assertThat(query.getQuery()) + .isEqualTo(BASE_SELECT + " WHERE UPPER(" + TABLE + ".\"FIRST_NAME\") = UPPER(:first_name)"); } @Test // DATAJDBC-318 @@ -525,8 +501,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE - + ".\"FIRST_NAME\" = :first_name LIMIT 3"; + String expectedSql = BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name LIMIT 3"; assertThat(query.getQuery()).isEqualTo(expectedSql); } @@ -538,8 +513,7 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "John" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE - + ".\"FIRST_NAME\" = :first_name LIMIT 1"; + String expectedSql = BASE_SELECT + " WHERE " + TABLE + ".\"FIRST_NAME\" = :first_name LIMIT 1"; assertThat(query.getQuery()).isEqualTo(expectedSql); } @@ -552,8 +526,8 @@ public class PartTreeJdbcQueryUnitTests { new Object[] { new Address("Hello", "World") }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE (" + TABLE - + ".\"USER_STREET\" = :user_street AND " + TABLE + ".\"USER_CITY\" = :user_city)"; + String expectedSql = BASE_SELECT + " WHERE (" + TABLE + ".\"USER_STREET\" = :user_street AND " + TABLE + + ".\"USER_CITY\" = :user_city)"; assertThat(query.getQuery()).isEqualTo(expectedSql); assertThat(query.getParameterSource().getValue("user_street")).isEqualTo("Hello"); @@ -568,16 +542,15 @@ public class PartTreeJdbcQueryUnitTests { RelationalParametersParameterAccessor accessor = getAccessor(queryMethod, new Object[] { "Hello" }); ParametrizedQuery query = jdbcQuery.createQuery(accessor); - String expectedSql = "SELECT " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE - + ".\"USER_STREET\" = :user_street"; + String expectedSql = BASE_SELECT + " WHERE " + TABLE + ".\"USER_STREET\" = :user_street"; assertThat(query.getQuery()).isEqualTo(expectedSql); assertThat(query.getParameterSource().getValue("user_street")).isEqualTo("Hello"); } private PartTreeJdbcQuery createQuery(JdbcQueryMethod queryMethod) { - return new PartTreeJdbcQuery(queryMethod, H2Dialect.INSTANCE, converter, mock(NamedParameterJdbcOperations.class), - mock(RowMapper.class)); + return new PartTreeJdbcQuery(mappingContext, queryMethod, H2Dialect.INSTANCE, converter, + mock(NamedParameterJdbcOperations.class), mock(RowMapper.class)); } private JdbcQueryMethod getQueryMethod(String methodName, Class... parameterTypes) throws Exception { diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java index 77a64644..99f82566 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/sql/DefaultSelectBuilder.java @@ -288,7 +288,6 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn private @Nullable Expression to; private @Nullable Condition condition; - JoinBuilder(Table table, DefaultSelectBuilder selectBuilder, JoinType joinType) { this.table = table; @@ -341,10 +340,10 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn } else { condition = condition.and(comparison); } + } private Join finishJoin() { - finishCondition(); return new Join(joinType, table, condition); }