Skip to content

Commit c563c2c

Browse files
committed
Move query extraction into QueryLookupStrategy.
JdbcStringBasedQuery no longer extracts the query from the `QueryMethod`. The logic of applying table name based SpEL expressions moves into the new base class `RelationalQueryLookupStrategy`. See #1856 Originial pull request #1863
1 parent f87866c commit c563c2c

File tree

10 files changed

+112
-48
lines changed

10 files changed

+112
-48
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethod.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ String getDeclaredQuery() {
128128
return StringUtils.hasText(annotatedValue) ? annotatedValue : getNamedQuery();
129129
}
130130

131-
String getRequiredQuery() {
131+
public String getRequiredQuery() {
132132

133133
String query = getDeclaredQuery();
134134

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StringBasedJdbcQuery.java

+8-9
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
113113
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
114114
RowMapperFactory rowMapperFactory, JdbcConverter converter,
115115
QueryMethodEvaluationContextProvider evaluationContextProvider) {
116-
this(queryMethod, operations, rowMapperFactory, converter, evaluationContextProvider, QueryPreprocessor.NOOP);
116+
this(queryMethod, operations, rowMapperFactory, converter, evaluationContextProvider, QueryPreprocessor.NOOP.transform(queryMethod.getRequiredQuery()));
117117
}
118118

119119
/**
@@ -125,12 +125,12 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
125125
* @param rowMapperFactory must not be {@literal null}.
126126
* @param converter must not be {@literal null}.
127127
* @param evaluationContextProvider must not be {@literal null}.
128-
* @param queryPreprocessor must not be {@literal null}.
128+
* @param query
129129
* @since 3.4
130130
*/
131131
public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
132-
RowMapperFactory rowMapperFactory, JdbcConverter converter,
133-
QueryMethodEvaluationContextProvider evaluationContextProvider, QueryPreprocessor queryPreprocessor) {
132+
RowMapperFactory rowMapperFactory, JdbcConverter converter,
133+
QueryMethodEvaluationContextProvider evaluationContextProvider, String query) {
134134

135135
super(queryMethod, operations);
136136

@@ -164,10 +164,9 @@ public StringBasedJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOpera
164164
.of((counter, expression) -> String.format("__$synthetic$__%d", counter + 1), String::concat)
165165
.withEvaluationContextProvider(evaluationContextProvider);
166166

167-
168-
this.query = queryPreprocessor.transform(queryMethod.getRequiredQuery());
169-
this.spelEvaluator = queryContext.parse(query, getQueryMethod().getParameters());
170-
this.containsSpelExpressions = !this.spelEvaluator.getQueryString().equals(query);
167+
this.query = query;
168+
this.spelEvaluator = queryContext.parse(this.query, getQueryMethod().getParameters());
169+
this.containsSpelExpressions = !this.spelEvaluator.getQueryString().equals(this.query);
171170
}
172171

173172
@Override
@@ -185,7 +184,7 @@ public Object execute(Object[] objects) {
185184
private String processSpelExpressions(Object[] objects, MapSqlParameterSource parameterMap) {
186185

187186
if (containsSpelExpressions) {
188-
// TODO: Make code changes here
187+
189188
spelEvaluator.evaluate(objects).forEach(parameterMap::addValue);
190189
return spelEvaluator.getQueryString();
191190
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcQueryLookupStrategy.java

+8-15
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,14 @@
2929
import org.springframework.data.jdbc.repository.query.JdbcQueryMethod;
3030
import org.springframework.data.jdbc.repository.query.PartTreeJdbcQuery;
3131
import org.springframework.data.jdbc.repository.query.StringBasedJdbcQuery;
32-
import org.springframework.data.relational.repository.query.QueryPreprocessor;
33-
import org.springframework.data.relational.repository.query.TableNameQueryPreprocessor;
3432
import org.springframework.data.mapping.callback.EntityCallbacks;
3533
import org.springframework.data.projection.ProjectionFactory;
3634
import org.springframework.data.relational.core.dialect.Dialect;
3735
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3836
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
3937
import org.springframework.data.relational.core.mapping.event.AfterConvertCallback;
4038
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
41-
import org.springframework.data.relational.core.sql.SqlIdentifier;
39+
import org.springframework.data.relational.repository.support.RelationalQueryLookupStrategy;
4240
import org.springframework.data.repository.core.NamedQueries;
4341
import org.springframework.data.repository.core.RepositoryMetadata;
4442
import org.springframework.data.repository.query.QueryLookupStrategy;
@@ -63,7 +61,7 @@
6361
* @author Diego Krupitza
6462
* @author Christopher Klein
6563
*/
66-
abstract class JdbcQueryLookupStrategy implements QueryLookupStrategy {
64+
abstract class JdbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
6765

6866
private static final Log LOG = LogFactory.getLog(JdbcQueryLookupStrategy.class);
6967

@@ -82,8 +80,10 @@ abstract class JdbcQueryLookupStrategy implements QueryLookupStrategy {
8280
QueryMappingConfiguration queryMappingConfiguration, NamedParameterJdbcOperations operations,
8381
@Nullable BeanFactory beanfactory, QueryMethodEvaluationContextProvider evaluationContextProvider) {
8482

83+
super(context, dialect);
84+
8585
Assert.notNull(publisher, "ApplicationEventPublisher must not be null");
86-
Assert.notNull(context, "RelationalMappingContextPublisher must not be null");
86+
Assert.notNull(context, "RelationalMappingContext must not be null");
8787
Assert.notNull(converter, "JdbcConverter must not be null");
8888
Assert.notNull(dialect, "Dialect must not be null");
8989
Assert.notNull(queryMappingConfiguration, "QueryMappingConfiguration must not be null");
@@ -159,24 +159,17 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
159159
"Query method %s is annotated with both, a query and a query name; Using the declared query", method));
160160
}
161161

162-
QueryPreprocessor queryPreprocessor = prepareQueryPreprocessor(repositoryMetadata);
162+
String queryString = evaluateTableExpressions(repositoryMetadata, queryMethod.getRequiredQuery());
163+
163164
StringBasedJdbcQuery query = new StringBasedJdbcQuery(queryMethod, getOperations(), this::createMapper,
164-
getConverter(), evaluationContextProvider,
165-
queryPreprocessor);
165+
getConverter(), evaluationContextProvider, queryString);
166166
query.setBeanFactory(getBeanFactory());
167167
return query;
168168
}
169169

170170
throw new IllegalStateException(
171171
String.format("Did neither find a NamedQuery nor an annotated query for method %s", method));
172172
}
173-
174-
private QueryPreprocessor prepareQueryPreprocessor(RepositoryMetadata repositoryMetadata) {
175-
176-
SqlIdentifier tableName = getContext().getPersistentEntity(repositoryMetadata.getDomainType()).getTableName();
177-
SqlIdentifier qualifiedTableName = getContext().getPersistentEntity(repositoryMetadata.getDomainType()).getQualifiedTableName();
178-
return new TableNameQueryPreprocessor(tableName, qualifiedTableName, getDialect());
179-
}
180173
}
181174

182175
/**

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/DeclaredQueryRepositoryUnitTests.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -52,31 +52,31 @@ public class DeclaredQueryRepositoryUnitTests {
5252

5353
private NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class, RETURNS_DEEP_STUBS);
5454

55-
@Test
55+
@Test // GH-1856
5656
void plainSql() {
5757

5858
repository(DummyEntityRepository.class).plainQuery();
5959

6060
assertThat(query()).isEqualTo("select * from someTable");
6161
}
6262

63-
@Test
63+
@Test // GH-1856
6464
void tableNameQuery() {
6565

6666
repository(DummyEntityRepository.class).tableNameQuery();
6767

6868
assertThat(query()).isEqualTo("select * from \"DUMMY_ENTITY\"");
6969
}
7070

71-
@Test
71+
@Test // GH-1856
7272
void renamedTableNameQuery() {
7373

7474
repository(RenamedEntityRepository.class).tableNameQuery();
7575

7676
assertThat(query()).isEqualTo("select * from \"ReNamed\"");
7777
}
7878

79-
@Test
79+
@Test // GH-1856
8080
void fullyQualifiedTableNameQuery() {
8181

8282
repository(RenamedEntityRepository.class).qualifiedTableNameQuery();

spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactory.java

+7-15
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,11 @@
2828
import org.springframework.data.r2dbc.repository.query.PartTreeR2dbcQuery;
2929
import org.springframework.data.r2dbc.repository.query.R2dbcQueryMethod;
3030
import org.springframework.data.r2dbc.repository.query.StringBasedR2dbcQuery;
31-
import org.springframework.data.relational.core.dialect.Dialect;
3231
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
3332
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
34-
import org.springframework.data.relational.core.sql.SqlIdentifier;
35-
import org.springframework.data.relational.repository.query.QueryPreprocessor;
3633
import org.springframework.data.relational.repository.query.RelationalEntityInformation;
37-
import org.springframework.data.relational.repository.query.TableNameQueryPreprocessor;
3834
import org.springframework.data.relational.repository.support.MappingRelationalEntityInformation;
35+
import org.springframework.data.relational.repository.support.RelationalQueryLookupStrategy;
3936
import org.springframework.data.repository.core.NamedQueries;
4037
import org.springframework.data.repository.core.RepositoryInformation;
4138
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -144,8 +141,9 @@ private <T, ID> RelationalEntityInformation<T, ID> getEntityInformation(Class<T>
144141
* {@link QueryLookupStrategy} to create R2DBC queries..
145142
*
146143
* @author Mark Paluch
144+
* @author Jens Schauder
147145
*/
148-
private static class R2dbcQueryLookupStrategy implements QueryLookupStrategy {
146+
private static class R2dbcQueryLookupStrategy extends RelationalQueryLookupStrategy {
149147

150148
private final R2dbcEntityOperations entityOperations;
151149
private final ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider;
@@ -156,35 +154,29 @@ private static class R2dbcQueryLookupStrategy implements QueryLookupStrategy {
156154
R2dbcQueryLookupStrategy(R2dbcEntityOperations entityOperations,
157155
ReactiveQueryMethodEvaluationContextProvider evaluationContextProvider, R2dbcConverter converter,
158156
ReactiveDataAccessStrategy dataAccessStrategy) {
157+
158+
super(converter.getMappingContext(), dataAccessStrategy.getDialect());
159+
159160
this.entityOperations = entityOperations;
160161
this.evaluationContextProvider = evaluationContextProvider;
161162
this.converter = converter;
162163
this.dataAccessStrategy = dataAccessStrategy;
163-
164164
}
165165

166166
@Override
167167
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
168168
NamedQueries namedQueries) {
169169

170170
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext = this.converter.getMappingContext();
171-
Dialect dialect = dataAccessStrategy.getDialect();
172171

173172
R2dbcQueryMethod queryMethod = new R2dbcQueryMethod(method, metadata, factory,
174173
mappingContext);
175174
String namedQueryName = queryMethod.getNamedQueryName();
176175

177-
Class<?> domainType = metadata.getDomainType();
178-
RelationalPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(domainType);
179-
SqlIdentifier tableName = entity.getTableName();
180-
SqlIdentifier qualifiedTableName = entity.getQualifiedTableName();
181-
182176
if (namedQueries.hasQuery(namedQueryName) || queryMethod.hasAnnotatedQuery()) {
183-
184-
QueryPreprocessor queryPreprocessor = new TableNameQueryPreprocessor(tableName, qualifiedTableName, dialect);
185177

186178
String query = namedQueries.hasQuery(namedQueryName) ? namedQueries.getQuery(namedQueryName) : queryMethod.getRequiredAnnotatedQuery();
187-
query = queryPreprocessor.transform(query);
179+
query = evaluateTableExpressions(metadata, query);
188180

189181
return new StringBasedR2dbcQuery(query, queryMethod, this.entityOperations, this.converter,
190182
this.dataAccessStrategy,

spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/support/R2dbcRepositoryFactoryUnitTests.java

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.data.r2dbc.convert.R2dbcConverter;
3030
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
3131
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
32+
import org.springframework.data.relational.core.dialect.AnsiDialect;
3233
import org.springframework.data.relational.repository.query.RelationalEntityInformation;
3334
import org.springframework.data.relational.repository.support.MappingRelationalEntityInformation;
3435
import org.springframework.data.repository.Repository;
@@ -38,6 +39,7 @@
3839
* Unit test for {@link R2dbcRepositoryFactory}.
3940
*
4041
* @author Mark Paluch
42+
* @author Jens Schauder
4143
*/
4244
@ExtendWith(MockitoExtension.class)
4345
public class R2dbcRepositoryFactoryUnitTests {
@@ -50,6 +52,7 @@ public class R2dbcRepositoryFactoryUnitTests {
5052
@BeforeEach
5153
@SuppressWarnings("unchecked")
5254
public void before() {
55+
5356
when(dataAccessStrategy.getConverter()).thenReturn(r2dbcConverter);
5457
}
5558

@@ -65,6 +68,8 @@ public void usesMappingRelationalEntityInformationIfMappingContextSet() {
6568
@Test
6669
public void createsRepositoryWithIdTypeLong() {
6770

71+
when(dataAccessStrategy.getDialect()).thenReturn(AnsiDialect.INSTANCE);
72+
6873
R2dbcRepositoryFactory factory = new R2dbcRepositoryFactory(databaseClient, dataAccessStrategy);
6974
MyPersonRepository repository = factory.getRepository(MyPersonRepository.class);
7075

spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/support/SqlInspectingR2dbcRepositoryUnitTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public void before() {
6363

6464
}
6565

66-
@Test
66+
@Test // GH-1856
6767
public void replacesSpelExpressionInQuery() {
6868

6969
recorder.addStubbing(SqlInspectingR2dbcRepositoryUnitTests::isSelect, List.of());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.relational.repository.support;
18+
19+
import org.springframework.data.mapping.context.MappingContext;
20+
import org.springframework.data.relational.core.dialect.Dialect;
21+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
22+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
23+
import org.springframework.data.relational.core.sql.SqlIdentifier;
24+
import org.springframework.data.relational.repository.query.QueryPreprocessor;
25+
import org.springframework.data.repository.core.RepositoryMetadata;
26+
import org.springframework.data.repository.query.QueryLookupStrategy;
27+
import org.springframework.util.Assert;
28+
29+
/**
30+
* Base class for R2DBC and JDBC {@link QueryLookupStrategy} implementations.
31+
*
32+
* @author Jens Schauder
33+
* @since 3.4
34+
*/
35+
public abstract class RelationalQueryLookupStrategy implements QueryLookupStrategy {
36+
37+
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context;
38+
private final Dialect dialect;
39+
40+
protected RelationalQueryLookupStrategy(
41+
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
42+
Dialect dialect) {
43+
44+
Assert.notNull(context, "RelationalMappingContext must not be null");
45+
Assert.notNull(dialect, "Dialect must not be null");
46+
47+
this.context = context;
48+
this.dialect = dialect;
49+
}
50+
51+
protected String evaluateTableExpressions(RepositoryMetadata repositoryMetadata, String queryString) {
52+
53+
return prepareQueryPreprocessor(repositoryMetadata).transform(queryString);
54+
}
55+
56+
private QueryPreprocessor prepareQueryPreprocessor(RepositoryMetadata repositoryMetadata) {
57+
58+
SqlIdentifier tableName = context.getPersistentEntity(repositoryMetadata.getDomainType()).getTableName();
59+
SqlIdentifier qualifiedTableName = context.getPersistentEntity(repositoryMetadata.getDomainType())
60+
.getQualifiedTableName();
61+
return new TableNameQueryPreprocessor(tableName, qualifiedTableName, dialect);
62+
}
63+
64+
}
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.data.relational.repository.query;
17+
package org.springframework.data.relational.repository.support;
1818

1919
import org.springframework.data.relational.core.dialect.Dialect;
2020
import org.springframework.data.relational.core.sql.SqlIdentifier;
21+
import org.springframework.data.relational.repository.query.QueryPreprocessor;
2122
import org.springframework.expression.Expression;
2223
import org.springframework.expression.ParserContext;
2324
import org.springframework.expression.spel.standard.SpelExpressionParser;
@@ -26,7 +27,12 @@
2627

2728
import java.util.regex.Pattern;
2829

29-
public class TableNameQueryPreprocessor implements QueryPreprocessor {
30+
/**
31+
* Replaces SpEL expressions based on table names in query strings.
32+
*
33+
* @author Jens Schauder
34+
*/
35+
class TableNameQueryPreprocessor implements QueryPreprocessor {
3036

3137
private static final String EXPRESSION_PARAMETER = "$1#{";
3238
private static final String QUOTED_EXPRESSION_PARAMETER = "$1__HASH__{";
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,18 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.data.relational.repository.query;
17+
package org.springframework.data.relational.repository.support;
1818

1919
import org.assertj.core.api.SoftAssertions;
2020
import org.junit.jupiter.api.Test;
2121
import org.springframework.data.relational.core.dialect.AnsiDialect;
2222
import org.springframework.data.relational.core.sql.SqlIdentifier;
2323

24+
/**
25+
* Tests for {@link TableNameQueryPreprocessor}.
26+
*
27+
* @author Jens Schauder
28+
*/
2429
class TableNameQueryPreprocessorUnitTests {
2530

2631
@Test // GH-1856

0 commit comments

Comments
 (0)