Skip to content

Commit fd98e16

Browse files
committed
DATAJDBC-318 - Adding a failing test.
Added failing tests for entities with references. Code deduplication. Documentation wording. Formatting. Original pull request: #209.
1 parent 999bf29 commit fd98e16

File tree

10 files changed

+71
-43
lines changed

10 files changed

+71
-43
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
import org.springframework.util.Assert;
2828

2929
/**
30-
* A query to be executed based on a repository method, it's annotated SQL query and the arguments provided to the
31-
* method.
30+
* Base class for queries based on a repository method. It holds the infrastructure for executing a query and knows how
31+
* to execute a query based on the return type of the method. How to construct the query is left to subclasses.
3232
*
3333
* @author Jens Schauder
3434
* @author Kazuki Shimizu

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
6565
* @param entityMetadata relational entity metadata, must not be {@literal null}.
6666
* @param accessor parameter metadata provider, must not be {@literal null}.
6767
*/
68-
public JdbcQueryCreator(PartTree tree, JdbcConverter converter, Dialect dialect,
68+
JdbcQueryCreator(PartTree tree, JdbcConverter converter, Dialect dialect,
6969
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor) {
7070
super(tree, accessor);
7171

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
import org.springframework.lang.Nullable;
2020

2121
/**
22-
* Interface specifying a result execution strategy.
22+
* Interface specifying a query execution strategy. Implementations encapsulate information how to actually execute the
23+
* query and how to process the result in order to get the desired return type.
2324
*
2425
* @author Mark Paluch
2526
* @since 2.0
@@ -28,11 +29,11 @@
2829
interface JdbcQueryExecution<T> {
2930

3031
/**
31-
* Execute the given {@code query}.
32+
* Execute the given {@code query} and {@code parameter} and transforms the result into a {@code T}.
3233
*
33-
* @param query
34-
* @param parameter
35-
* @return
34+
* @param query the query to be executed. Must not be {@literal null}.
35+
* @param parameter the parameters to be bound to the query. Must not be {@literal null}.
36+
* @return the result of the query. Might be {@literal null}.
3637
*/
3738
@Nullable
3839
T execute(String query, SqlParameterSource parameter);

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
1919

2020
/**
21-
* Value object encapsulating a parametrized query containing named parameters and {@link SqlParameterSource}.
21+
* Value object encapsulating a query containing named parameters and a{@link SqlParameterSource} to bind the parameters.
2222
*
2323
* @author Mark Paluch
2424
* @since 2.0
@@ -28,16 +28,17 @@ class ParametrizedQuery {
2828
private final String query;
2929
private final SqlParameterSource parameterSource;
3030

31-
public ParametrizedQuery(String query, SqlParameterSource parameterSource) {
31+
ParametrizedQuery(String query, SqlParameterSource parameterSource) {
32+
3233
this.query = query;
3334
this.parameterSource = parameterSource;
3435
}
3536

36-
public String getQuery() {
37+
String getQuery() {
3738
return query;
3839
}
3940

40-
public SqlParameterSource getParameterSource() {
41+
SqlParameterSource getParameterSource() {
4142
return parameterSource;
4243
}
4344

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ public PartTreeJdbcQuery(JdbcQueryMethod queryMethod, Dialect dialect, JdbcConve
6464
this.converter = converter;
6565

6666
try {
67+
6768
this.tree = new PartTree(queryMethod.getName(), queryMethod.getEntityInformation().getJavaType());
6869
JdbcQueryCreator.validate(this.tree, this.parameters);
6970
} catch (RuntimeException e) {
71+
7072
throw new IllegalArgumentException(
7173
String.format("Failed to create query for method %s! %s", queryMethod, e.getMessage()), e);
7274
}

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

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class QueryMapper {
6969
* @param converter must not be {@literal null}.
7070
*/
7171
@SuppressWarnings({ "unchecked", "rawtypes" })
72-
public QueryMapper(Dialect dialect, JdbcConverter converter) {
72+
QueryMapper(Dialect dialect, JdbcConverter converter) {
7373

7474
Assert.notNull(dialect, "Dialect must not be null!");
7575
Assert.notNull(converter, "JdbcConverter must not be null!");
@@ -80,13 +80,13 @@ public QueryMapper(Dialect dialect, JdbcConverter converter) {
8080
}
8181

8282
/**
83-
* Map the {@link Sort} object to apply field name mapping using {@link Class the type to read}.
83+
* Map the {@link Sort} object to apply field name mapping using {@link RelationalPersistentEntity the type to read}.
8484
*
8585
* @param sort must not be {@literal null}.
8686
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
8787
* @return
8888
*/
89-
public List<OrderByField> getMappedSort(Table table, Sort sort, @Nullable RelationalPersistentEntity<?> entity) {
89+
List<OrderByField> getMappedSort(Table table, Sort sort, @Nullable RelationalPersistentEntity<?> entity) {
9090

9191
List<OrderByField> mappedOrder = new ArrayList<>();
9292

@@ -102,13 +102,14 @@ public List<OrderByField> getMappedSort(Table table, Sort sort, @Nullable Relati
102102
}
103103

104104
/**
105-
* Map the {@link Expression} object to apply field name mapping using {@link Class the type to read}.
105+
* Map the {@link Expression} object to apply field name mapping using {@link RelationalPersistentEntity the type to
106+
* read}.
106107
*
107108
* @param expression must not be {@literal null}.
108109
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
109-
* @return the mapped {@link Expression}.
110+
* @return the mapped {@link Expression}. Guaranteed to be not {@literal null}.
110111
*/
111-
public Expression getMappedObject(Expression expression, @Nullable RelationalPersistentEntity<?> entity) {
112+
Expression getMappedObject(Expression expression, @Nullable RelationalPersistentEntity<?> entity) {
112113

113114
if (entity == null || expression instanceof AsteriskFromTable) {
114115
return expression;
@@ -120,6 +121,8 @@ public Expression getMappedObject(Expression expression, @Nullable RelationalPer
120121
Field field = createPropertyField(entity, column.getName());
121122
Table table = column.getTable();
122123

124+
Assert.state(table != null, String.format("The column %s must have a table set.", column));
125+
123126
Column columnFromTable = table.column(field.getMappedColumnName());
124127
return column instanceof Aliased ? columnFromTable.as(((Aliased) column).getAlias()) : columnFromTable;
125128
}
@@ -144,15 +147,15 @@ public Expression getMappedObject(Expression expression, @Nullable RelationalPer
144147
}
145148

146149
/**
147-
* Map a {@link CriteriaDefinition} object into {@link Condition} and consider value/{@code NULL} {@link Bindings}.
150+
* Map a {@link CriteriaDefinition} object into {@link Condition} and consider value/{@code NULL} bindings.
148151
*
149152
* @param parameterSource bind parameterSource object, must not be {@literal null}.
150153
* @param criteria criteria definition to map, must not be {@literal null}.
151154
* @param table must not be {@literal null}.
152155
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
153156
* @return the mapped {@link Condition}.
154157
*/
155-
public Condition getMappedObject(MapSqlParameterSource parameterSource, CriteriaDefinition criteria, Table table,
158+
Condition getMappedObject(MapSqlParameterSource parameterSource, CriteriaDefinition criteria, Table table,
156159
@Nullable RelationalPersistentEntity<?> entity) {
157160

158161
Assert.notNull(parameterSource, "MapSqlParameterSource must not be null!");
@@ -349,13 +352,13 @@ protected Object convertValue(@Nullable Object value, TypeInformation<?> typeInf
349352

350353
Pair<Object, Object> pair = (Pair<Object, Object>) value;
351354

352-
Object first = convertValue(pair.getFirst(),
353-
typeInformation.getActualType() != null ? typeInformation.getRequiredActualType()
354-
: ClassTypeInformation.OBJECT);
355+
Object first = convertValue(pair.getFirst(), typeInformation.getActualType() != null //
356+
? typeInformation.getRequiredActualType()
357+
: ClassTypeInformation.OBJECT);
355358

356-
Object second = convertValue(pair.getSecond(),
357-
typeInformation.getActualType() != null ? typeInformation.getRequiredActualType()
358-
: ClassTypeInformation.OBJECT);
359+
Object second = convertValue(pair.getSecond(), typeInformation.getActualType() != null //
360+
? typeInformation.getRequiredActualType()
361+
: ClassTypeInformation.OBJECT);
359362

360363
return Pair.of(first, second);
361364
}
@@ -365,6 +368,7 @@ protected Object convertValue(@Nullable Object value, TypeInformation<?> typeInf
365368
List<Object> mapped = new ArrayList<>();
366369

367370
for (Object o : (Iterable<?>) value) {
371+
368372
mapped.add(convertValue(o, typeInformation.getActualType() != null ? typeInformation.getRequiredActualType()
369373
: ClassTypeInformation.OBJECT));
370374
}
@@ -498,10 +502,6 @@ Field createPropertyField(@Nullable RelationalPersistentEntity<?> entity, SqlIde
498502
return entity == null ? new Field(key) : new MetadataBackedField(key, entity, mappingContext, converter);
499503
}
500504

501-
Class<?> getTypeHint(@Nullable Object mappedValue, Class<?> propertyType) {
502-
return propertyType;
503-
}
504-
505505
int getTypeHint(@Nullable Object mappedValue, Class<?> propertyType, JdbcValue settableValue) {
506506

507507
if (mappedValue == null || propertyType.equals(Object.class)) {
@@ -560,7 +560,7 @@ protected static class Field {
560560
*
561561
* @param name must not be {@literal null} or empty.
562562
*/
563-
public Field(SqlIdentifier name) {
563+
Field(SqlIdentifier name) {
564564

565565
Assert.notNull(name, "Name must not be null!");
566566
this.name = name;

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,7 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata repository
9494
JdbcQueryMethod queryMethod = new JdbcQueryMethod(method, repositoryMetadata, projectionFactory, namedQueries,
9595
context);
9696

97-
if (namedQueries.hasQuery(queryMethod.getNamedQueryName())) {
98-
99-
RowMapper<?> mapper = queryMethod.isModifyingQuery() ? null : createMapper(queryMethod);
100-
return new StringBasedJdbcQuery(queryMethod, operations, mapper, converter);
101-
} else if (queryMethod.hasAnnotatedQuery()) {
97+
if (namedQueries.hasQuery(queryMethod.getNamedQueryName()) || queryMethod.hasAnnotatedQuery()) {
10298

10399
RowMapper<?> mapper = queryMethod.isModifyingQuery() ? null : createMapper(queryMethod);
104100
return new StringBasedJdbcQuery(queryMethod, operations, mapper, converter);

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

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import static org.mockito.Mockito.*;
2020

2121
import lombok.AllArgsConstructor;
22-
import lombok.Data;
2322

2423
import java.lang.reflect.Method;
2524
import java.util.Collection;
@@ -31,7 +30,6 @@
3130
import org.junit.Test;
3231
import org.junit.runner.RunWith;
3332
import org.mockito.junit.MockitoJUnitRunner;
34-
3533
import org.springframework.data.annotation.Id;
3634
import org.springframework.data.jdbc.core.convert.BasicJdbcConverter;
3735
import org.springframework.data.jdbc.core.convert.JdbcConverter;
@@ -63,6 +61,26 @@ public class PartTreeJdbcQueryUnitTests {
6361
JdbcMappingContext mappingContext = new JdbcMappingContext();
6462
JdbcConverter converter = new BasicJdbcConverter(mappingContext, mock(RelationResolver.class));
6563

64+
@Test // DATAJDBC-318
65+
public void selectContainsColumnsForOneToOneReference() throws Exception {
66+
67+
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
68+
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
69+
ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" }));
70+
71+
assertThat(query.getQuery()).contains("hated.\"name\" AS \"hated_name\"");
72+
}
73+
74+
@Test // DATAJDBC-318
75+
public void doesNotContainsColumnsForOneToManyReference() throws Exception{
76+
77+
JdbcQueryMethod queryMethod = getQueryMethod("findAllByFirstName", String.class);
78+
PartTreeJdbcQuery jdbcQuery = createQuery(queryMethod);
79+
ParametrizedQuery query = jdbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" }));
80+
81+
assertThat(query.getQuery().toLowerCase()).doesNotContain("hobbies");
82+
}
83+
6684
@Test // DATAJDBC-318
6785
public void createsQueryToFindAllEntitiesByStringAttribute() throws Exception {
6886

@@ -622,7 +640,6 @@ interface UserRepository extends Repository<User, Long> {
622640
}
623641

624642
@Table("users")
625-
@Data
626643
static class User {
627644

628645
@Id Long id;
@@ -633,12 +650,18 @@ static class User {
633650
Boolean active;
634651

635652
@Embedded(prefix = "user_", onEmpty = Embedded.OnEmpty.USE_NULL) Address address;
653+
654+
List<Hobby> hobbies;
655+
Hobby hated;
636656
}
637657

638-
@Data
639658
@AllArgsConstructor
640659
static class Address {
641660
String street;
642661
String city;
643662
}
663+
664+
static class Hobby {
665+
String name;
666+
}
644667
}

spring-data-relational/src/main/java/org/springframework/data/relational/repository/query/RelationalQueryCreator.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@ public static void validate(PartTree tree, Parameters<?, ?> parameters) {
103103

104104
Iterable<Part> parts = () -> tree.stream().flatMap(Streamable::stream).iterator();
105105
for (Part part : parts) {
106+
106107
int numberOfArguments = part.getNumberOfArguments();
107108
for (int i = 0; i < numberOfArguments; i++) {
109+
108110
throwExceptionOnArgumentMismatch(part, parameters, argCount);
109111
argCount++;
110112
}
@@ -117,6 +119,7 @@ private static void throwExceptionOnArgumentMismatch(Part part, Parameters<?, ?>
117119
String property = part.getProperty().toDotPath();
118120

119121
if (!parameters.getBindableParameters().hasParameterAt(index)) {
122+
120123
String msgTemplate = "Query method expects at least %d arguments but only found %d. "
121124
+ "This leaves an operator of type %s for property %s unbound.";
122125
String formattedMsg = String.format(msgTemplate, index + 1, index, type.name(), property);
@@ -125,9 +128,11 @@ private static void throwExceptionOnArgumentMismatch(Part part, Parameters<?, ?>
125128

126129
Parameter parameter = parameters.getBindableParameter(index);
127130
if (expectsCollection(type) && !parameterIsCollectionLike(parameter)) {
131+
128132
String message = wrongParameterTypeMessage(property, type, "Collection", parameter);
129133
throw new IllegalStateException(message);
130134
} else if (!expectsCollection(type) && !parameterIsScalarLike(parameter)) {
135+
131136
String message = wrongParameterTypeMessage(property, type, "scalar", parameter);
132137
throw new IllegalStateException(message);
133138
}

src/main/asciidoc/jdbc.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ Thus, the method name results in a query expression of `SELECT … FROM person W
447447
<2> Use `Pageable` to pass offset and sorting parameters to the database.
448448
<3> Find a single entity for the given criteria.
449449
It completes with `IncorrectResultSizeDataAccessException` on non-unique results.
450-
<4> Unless <3>, the first entity is always emitted even if the query yields more result documents.
450+
<4> In contrast to <3>, the first entity is always emitted even if the query yields more result documents.
451451
<5> The `findByLastname` method shows a query for all people with the given last name.
452452
====
453453

@@ -541,7 +541,7 @@ The following table shows the keywords that are supported for query methods:
541541
| `active IS FALSE`
542542
|===
543543

544-
NOTE: Query derivation is limited to properties that can be used in a `WHERE` clause without involving joins.
544+
NOTE: Query derivation is limited to properties that can be used in a `WHERE` clause without using joins.
545545

546546
[[jdbc.query-methods.strategies]]
547547
=== Query Lookup Strategies
@@ -622,7 +622,7 @@ Iterating happens in the order of registration, so make sure to register more ge
622622
If applicable, wrapper types such as collections or `Optional` are unwrapped.
623623
Thus, a return type of `Optional<Person>` uses the `Person` type in the preceding process.
624624

625-
NOTE: Using a custom `RowMapper` through `QueryMappingConfiguration`, `@Query(rowMapperClass=…)`, or a custom `ResultSetExtractor` disables Entity Callbacks and Lifecycle Events as these components are under full control of the result mapping and can issue their own events/callbacks if needed.
625+
NOTE: Using a custom `RowMapper` through `QueryMappingConfiguration`, `@Query(rowMapperClass=…)`, or a custom `ResultSetExtractor` disables Entity Callbacks and Lifecycle Events as the result mapping can issue its own events/callbacks if needed.
626626

627627
[[jdbc.query-methods.at-query.modifying]]
628628
==== Modifying Query

0 commit comments

Comments
 (0)