Skip to content

Commit 999bf29

Browse files
mp911deschauder
authored andcommitted
DATAJDBC-318 - Initial support for query derivation.
Move JdbcRepositoryQuery into repository.query package. Split JdbcRepositoryQuery into AbstractJdbcQuery and StringBasedJdbcQuery. Add QueryMapper for mapping of Criteria. Initial support for query derivation. Emit events and issue entity callbacks only for default RowMapper. Custom RowMapper/ResultSetExtractor are in full control of the mapping and can issue events/callbacks themselves. Update reference documentation. Original pull request: #209.
1 parent 2f3f00b commit 999bf29

File tree

40 files changed

+3263
-839
lines changed

40 files changed

+3263
-839
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Copyright 2020 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+
package org.springframework.data.jdbc.repository.query;
17+
18+
import java.util.List;
19+
20+
import org.springframework.dao.EmptyResultDataAccessException;
21+
import org.springframework.data.repository.query.RepositoryQuery;
22+
import org.springframework.jdbc.core.ResultSetExtractor;
23+
import org.springframework.jdbc.core.RowMapper;
24+
import org.springframework.jdbc.core.RowMapperResultSetExtractor;
25+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
26+
import org.springframework.lang.Nullable;
27+
import org.springframework.util.Assert;
28+
29+
/**
30+
* A query to be executed based on a repository method, it's annotated SQL query and the arguments provided to the
31+
* method.
32+
*
33+
* @author Jens Schauder
34+
* @author Kazuki Shimizu
35+
* @author Oliver Gierke
36+
* @author Maciej Walkowiak
37+
* @author Mark Paluch
38+
* @since 2.0
39+
*/
40+
public abstract class AbstractJdbcQuery implements RepositoryQuery {
41+
42+
private final JdbcQueryMethod queryMethod;
43+
private final NamedParameterJdbcOperations operations;
44+
45+
/**
46+
* Creates a new {@link AbstractJdbcQuery} for the given {@link JdbcQueryMethod}, {@link NamedParameterJdbcOperations}
47+
* and {@link RowMapper}.
48+
*
49+
* @param queryMethod must not be {@literal null}.
50+
* @param operations must not be {@literal null}.
51+
* @param defaultRowMapper can be {@literal null} (only in case of a modifying query).
52+
*/
53+
AbstractJdbcQuery(JdbcQueryMethod queryMethod, NamedParameterJdbcOperations operations,
54+
@Nullable RowMapper<?> defaultRowMapper) {
55+
56+
Assert.notNull(queryMethod, "Query method must not be null!");
57+
Assert.notNull(operations, "NamedParameterJdbcOperations must not be null!");
58+
59+
if (!queryMethod.isModifyingQuery()) {
60+
Assert.notNull(defaultRowMapper, "Mapper must not be null!");
61+
}
62+
63+
this.queryMethod = queryMethod;
64+
this.operations = operations;
65+
}
66+
67+
/*
68+
* (non-Javadoc)
69+
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
70+
*/
71+
@Override
72+
public JdbcQueryMethod getQueryMethod() {
73+
return queryMethod;
74+
}
75+
76+
/**
77+
* Creates a {@link JdbcQueryExecution} given {@link JdbcQueryMethod}, {@link ResultSetExtractor} an
78+
* {@link RowMapper}. Prefers the given {@link ResultSetExtractor} over {@link RowMapper}.
79+
*
80+
* @param queryMethod must not be {@literal null}.
81+
* @param extractor must not be {@literal null}.
82+
* @param rowMapper must not be {@literal null}.
83+
* @return
84+
*/
85+
protected JdbcQueryExecution<?> getQueryExecution(JdbcQueryMethod queryMethod,
86+
@Nullable ResultSetExtractor<Object> extractor, RowMapper<Object> rowMapper) {
87+
88+
if (queryMethod.isModifyingQuery()) {
89+
return createModifyingQueryExecutor();
90+
}
91+
92+
if (queryMethod.isCollectionQuery() || queryMethod.isStreamQuery()) {
93+
return extractor != null ? getQueryExecution(extractor) : collectionQuery(rowMapper);
94+
}
95+
96+
return extractor != null ? getQueryExecution(extractor) : singleObjectQuery(rowMapper);
97+
}
98+
99+
private JdbcQueryExecution<Object> createModifyingQueryExecutor() {
100+
101+
return (query, parameters) -> {
102+
103+
int updatedCount = operations.update(query, parameters);
104+
Class<?> returnedObjectType = queryMethod.getReturnedObjectType();
105+
106+
return (returnedObjectType == boolean.class || returnedObjectType == Boolean.class) ? updatedCount != 0
107+
: updatedCount;
108+
};
109+
}
110+
111+
private JdbcQueryExecution<Object> singleObjectQuery(RowMapper<?> rowMapper) {
112+
113+
return (query, parameters) -> {
114+
try {
115+
return operations.queryForObject(query, parameters, rowMapper);
116+
} catch (EmptyResultDataAccessException e) {
117+
return null;
118+
}
119+
};
120+
}
121+
122+
private <T> JdbcQueryExecution<List<T>> collectionQuery(RowMapper<T> rowMapper) {
123+
return getQueryExecution(new RowMapperResultSetExtractor<>(rowMapper));
124+
}
125+
126+
private <T> JdbcQueryExecution<T> getQueryExecution(ResultSetExtractor<T> resultSetExtractor) {
127+
return (query, parameters) -> operations.query(query, parameters, resultSetExtractor);
128+
}
129+
130+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright 2020 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+
package org.springframework.data.jdbc.repository.query;
17+
18+
import java.util.ArrayList;
19+
import java.util.Collection;
20+
21+
import org.springframework.data.domain.Pageable;
22+
import org.springframework.data.domain.Sort;
23+
import org.springframework.data.jdbc.core.convert.JdbcConverter;
24+
import org.springframework.data.mapping.context.MappingContext;
25+
import org.springframework.data.relational.core.dialect.Dialect;
26+
import org.springframework.data.relational.core.dialect.RenderContextFactory;
27+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
28+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
29+
import org.springframework.data.relational.core.query.Criteria;
30+
import org.springframework.data.relational.core.sql.Select;
31+
import org.springframework.data.relational.core.sql.SelectBuilder;
32+
import org.springframework.data.relational.core.sql.SqlIdentifier;
33+
import org.springframework.data.relational.core.sql.Table;
34+
import org.springframework.data.relational.core.sql.render.SqlRenderer;
35+
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
36+
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
37+
import org.springframework.data.relational.repository.query.RelationalQueryCreator;
38+
import org.springframework.data.repository.query.parser.PartTree;
39+
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
40+
import org.springframework.util.Assert;
41+
42+
/**
43+
* Implementation of {@link RelationalQueryCreator} that creates {@link ParametrizedQuery} from a {@link PartTree}.
44+
*
45+
* @author Mark Paluch
46+
* @since 2.0
47+
*/
48+
class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
49+
50+
private final PartTree tree;
51+
private final RelationalParameterAccessor accessor;
52+
private final QueryMapper queryMapper;
53+
54+
private final MappingContext<RelationalPersistentEntity<?>, RelationalPersistentProperty> mappingContext;
55+
private final RelationalEntityMetadata<?> entityMetadata;
56+
private final RenderContextFactory renderContextFactory;
57+
58+
/**
59+
* Creates new instance of this class with the given {@link PartTree}, {@link JdbcConverter}, {@link Dialect},
60+
* {@link RelationalEntityMetadata} and {@link RelationalParameterAccessor}.
61+
*
62+
* @param tree part tree, must not be {@literal null}.
63+
* @param converter must not be {@literal null}.
64+
* @param dialect must not be {@literal null}.
65+
* @param entityMetadata relational entity metadata, must not be {@literal null}.
66+
* @param accessor parameter metadata provider, must not be {@literal null}.
67+
*/
68+
public JdbcQueryCreator(PartTree tree, JdbcConverter converter, Dialect dialect,
69+
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor) {
70+
super(tree, accessor);
71+
72+
Assert.notNull(converter, "JdbcConverter must not be null");
73+
Assert.notNull(dialect, "Dialect must not be null");
74+
Assert.notNull(entityMetadata, "Relational entity metadata must not be null");
75+
76+
this.tree = tree;
77+
this.accessor = accessor;
78+
79+
this.mappingContext = (MappingContext) converter.getMappingContext();
80+
this.entityMetadata = entityMetadata;
81+
this.queryMapper = new QueryMapper(dialect, converter);
82+
this.renderContextFactory = new RenderContextFactory(dialect);
83+
}
84+
85+
/**
86+
* Creates {@link ParametrizedQuery} applying the given {@link Criteria} and {@link Sort} definition.
87+
*
88+
* @param criteria {@link Criteria} to be applied to query
89+
* @param sort sort option to be applied to query, must not be {@literal null}.
90+
* @return instance of {@link ParametrizedQuery}
91+
*/
92+
@Override
93+
protected ParametrizedQuery complete(Criteria criteria, Sort sort) {
94+
95+
RelationalPersistentEntity<?> entity = entityMetadata.getTableEntity();
96+
Table table = Table.create(entityMetadata.getTableName());
97+
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
98+
99+
SelectBuilder.SelectFromAndJoin builder = Select.builder().select(table.columns(getSelectProjection())).from(table);
100+
101+
if (tree.isExistsProjection()) {
102+
builder = builder.limit(1);
103+
} else if (tree.isLimiting()) {
104+
builder = builder.limit(tree.getMaxResults());
105+
}
106+
107+
Pageable pageable = accessor.getPageable();
108+
if (pageable.isPaged()) {
109+
builder = builder.limit(pageable.getPageSize()).offset(pageable.getOffset());
110+
}
111+
112+
if (criteria != null) {
113+
builder.where(queryMapper.getMappedObject(parameterSource, criteria, table, entity));
114+
}
115+
116+
if (sort.isSorted()) {
117+
builder.orderBy(queryMapper.getMappedSort(table, sort, entity));
118+
}
119+
120+
Select select = builder.build();
121+
122+
String sql = SqlRenderer.create(renderContextFactory.createRenderContext()).render(select);
123+
124+
return new ParametrizedQuery(sql, parameterSource);
125+
}
126+
127+
private SqlIdentifier[] getSelectProjection() {
128+
129+
RelationalPersistentEntity<?> tableEntity = entityMetadata.getTableEntity();
130+
131+
if (tree.isExistsProjection()) {
132+
return new SqlIdentifier[] { tableEntity.getIdColumn() };
133+
}
134+
135+
Collection<SqlIdentifier> columnNames = unwrapColumnNames("", tableEntity);
136+
137+
return columnNames.toArray(new SqlIdentifier[0]);
138+
}
139+
140+
private Collection<SqlIdentifier> unwrapColumnNames(String prefix, RelationalPersistentEntity<?> persistentEntity) {
141+
142+
Collection<SqlIdentifier> columnNames = new ArrayList<>();
143+
144+
for (RelationalPersistentProperty property : persistentEntity) {
145+
146+
if (property.isEmbedded()) {
147+
columnNames.addAll(
148+
unwrapColumnNames(prefix + property.getEmbeddedPrefix(), mappingContext.getPersistentEntity(property)));
149+
}
150+
151+
else {
152+
columnNames.add(property.getColumnName().transform(prefix::concat));
153+
}
154+
}
155+
156+
return columnNames;
157+
}
158+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2020 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+
package org.springframework.data.jdbc.repository.query;
17+
18+
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
19+
import org.springframework.lang.Nullable;
20+
21+
/**
22+
* Interface specifying a result execution strategy.
23+
*
24+
* @author Mark Paluch
25+
* @since 2.0
26+
*/
27+
@FunctionalInterface
28+
interface JdbcQueryExecution<T> {
29+
30+
/**
31+
* Execute the given {@code query}.
32+
*
33+
* @param query
34+
* @param parameter
35+
* @return
36+
*/
37+
@Nullable
38+
T execute(String query, SqlParameterSource parameter);
39+
}

0 commit comments

Comments
 (0)