Skip to content

Commit 1156e70

Browse files
committed
Support of QueryByExampleExecutor#findAll(...).
This commit introduces the find by example method `findAll(...)` to spring-data-jdbc. MyBatis implementation is missing since I do not have the knowledge for this. Related tickets spring-projects#1192
1 parent 676cae2 commit 1156e70

File tree

11 files changed

+156
-24
lines changed

11 files changed

+156
-24
lines changed

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

+10
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,14 @@ public interface JdbcAggregateOperations {
167167
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
168168
*/
169169
<T> Optional<T> selectOne(Example<T> example);
170+
171+
/**
172+
* Execute a {@code SELECT} query and convert the resulting items to a {@link Iterable} that is sorted.
173+
*
174+
* @param example must not be null
175+
* @param sort the sorting that should be used on the result.
176+
* @return a non-null sorted list with all the matching results.
177+
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
178+
*/
179+
<T> Iterable<T> select(Example<T> example, Sort sort);
170180
}

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

+7
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,13 @@ public <T> Optional<T> selectOne(Example<T> example) {
270270
return accessStrategy.selectOne(query, probeType);
271271
}
272272

273+
@Override public <T> Iterable<T> select(Example<T> example, Sort sort) {
274+
Query query = this.exampleMapper.getMappedExample(example).sort(sort);
275+
Class<T> probeType = example.getProbeType();
276+
277+
return accessStrategy.select(query, probeType);
278+
}
279+
273280
/*
274281
* (non-Javadoc)
275282
* @see org.springframework.data.jdbc.core.JdbcAggregateOperations#findAll(java.lang.Class)

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

+5
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ public <T> Optional<T> selectOne(Query query, Class<T> probeType) {
216216
return collect(das -> das.selectOne(query, probeType));
217217
}
218218

219+
@Override
220+
public <T> Iterable<T> select(Query query, Class<T> probeType) {
221+
return collect(das -> das.select(query, probeType));
222+
}
223+
219224
private <T> T collect(Function<DataAccessStrategy, T> function) {
220225

221226
// Keep <T> as Eclipse fails to compile if <> is used.

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

+9
Original file line numberDiff line numberDiff line change
@@ -239,4 +239,13 @@ Iterable<Object> findAllByPath(Identifier identifier,
239239
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
240240
*/
241241
<T> Optional<T> selectOne(Query query, Class<T> probeType);
242+
243+
/**
244+
* Execute a {@code SELECT} query and convert the resulting items to a {@link Iterable}.
245+
*
246+
* @param query must not be {@literal null}.
247+
* @return a non null list with all the matching results.
248+
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
249+
*/
250+
<T> Iterable<T> select(Query query, Class<T> probeType);
242251
}

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ public <T> Iterable<T> findAll(Class<T> domainType, Pageable pageable) {
437437
@Override
438438
public <T> Optional<T> selectOne(Query query, Class<T> probeType) {
439439
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
440-
String sqlQuery = sql(probeType).selectOne(query, parameterSource);
440+
String sqlQuery = sql(probeType).selectByQuery(query, parameterSource);
441441

442442
T foundObject;
443443
try {
@@ -449,6 +449,14 @@ public <T> Optional<T> selectOne(Query query, Class<T> probeType) {
449449
return Optional.ofNullable(foundObject);
450450
}
451451

452+
@Override
453+
public <T> Iterable<T> select(Query query, Class<T> probeType) {
454+
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
455+
String sqlQuery = sql(probeType).selectByQuery(query, parameterSource);
456+
457+
return operations.query(sqlQuery, parameterSource, (RowMapper<T>) getEntityRowMapper(probeType));
458+
}
459+
452460
private <S, T> SqlIdentifierParameterSource getParameterSource(@Nullable S instance,
453461
RelationalPersistentEntity<S> persistentEntity, String prefix,
454462
Predicate<RelationalPersistentProperty> skipProperty, IdentifierProcessing identifierProcessing) {

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

+5
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ public <T> Optional<T> selectOne(Query query, Class<T> probeType) {
212212
return delegate.selectOne(query, probeType);
213213
}
214214

215+
@Override
216+
public <T> Iterable<T> select(Query query, Class<T> probeType) {
217+
return delegate.select(query, probeType);
218+
}
219+
215220
/**
216221
* Must be called exactly once before calling any of the other methods.
217222
*

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

+7-11
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ private OrderByField orderToOrderByField(Sort.Order order) {
733733
* @param parameterSource the source for holding the bindings
734734
* @return a non null query string.
735735
*/
736-
public String selectOne(Query query, MapSqlParameterSource parameterSource) {
736+
public String selectByQuery(Query query, MapSqlParameterSource parameterSource) {
737737

738738
Assert.notNull(parameterSource, "parameterSource must not be null");
739739

@@ -811,12 +811,12 @@ Column getParentId() {
811811
@Override
812812
public boolean equals(Object o) {
813813

814-
if (this == o) return true;
815-
if (o == null || getClass() != o.getClass()) return false;
814+
if (this == o)
815+
return true;
816+
if (o == null || getClass() != o.getClass())
817+
return false;
816818
Join join = (Join) o;
817-
return joinTable.equals(join.joinTable) &&
818-
joinColumn.equals(join.joinColumn) &&
819-
parentId.equals(join.parentId);
819+
return joinTable.equals(join.joinTable) && joinColumn.equals(join.joinColumn) && parentId.equals(join.parentId);
820820
}
821821

822822
@Override
@@ -827,11 +827,7 @@ public int hashCode() {
827827
@Override
828828
public String toString() {
829829

830-
return "Join{" +
831-
"joinTable=" + joinTable +
832-
", joinColumn=" + joinColumn +
833-
", parentId=" + parentId +
834-
'}';
830+
return "Join{" + "joinTable=" + joinTable + ", joinColumn=" + joinColumn + ", parentId=" + parentId + '}';
835831
}
836832
}
837833

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java

+7
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,13 @@ public <T> Optional<T> selectOne(Query query, Class<T> probeType) {
374374
return Optional.empty();
375375
}
376376

377+
@Override
378+
public <T> Iterable<T> select(Query query, Class<T> probeType) {
379+
// TODO: DIEGO find help for this one
380+
// I have zero MyBatis knowledge.
381+
return null;
382+
}
383+
377384
/*
378385
* (non-Javadoc)
379386
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)

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

+3-7
Original file line numberDiff line numberDiff line change
@@ -206,14 +206,10 @@ public <S extends T> Iterable<S> findAll(Example<S> example) {
206206

207207
@Override
208208
public <S extends T> Iterable<S> findAll(Example<S> example, Sort sort) {
209-
/*Assert.notNull(example, "Example must not be null!");
209+
Assert.notNull(example, "Example must not be null!");
210210
Assert.notNull(sort, "Sort must not be null!");
211-
212-
Query query = this.exampleMapper.getMappedExample(example).sort(sort);
213-
214-
return this.entityOperations.select(query, example.getProbeType());*/
215-
// TODO: impl
216-
return null;
211+
212+
return this.entityOperations.select(example,sort);
217213
}
218214

219215
@Override

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

+32-4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.data.annotation.Id;
2929
import org.springframework.data.annotation.ReadOnlyProperty;
3030
import org.springframework.data.annotation.Version;
31+
import org.springframework.data.domain.Example;
3132
import org.springframework.data.domain.PageRequest;
3233
import org.springframework.data.domain.Pageable;
3334
import org.springframework.data.domain.Sort;
@@ -46,10 +47,13 @@
4647
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
4748
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
4849
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
50+
import org.springframework.data.relational.core.query.Criteria;
51+
import org.springframework.data.relational.core.query.Query;
4952
import org.springframework.data.relational.core.sql.Aliased;
5053
import org.springframework.data.relational.core.sql.LockMode;
5154
import org.springframework.data.relational.core.sql.SqlIdentifier;
5255
import org.springframework.data.relational.core.sql.Table;
56+
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
5357

5458
/**
5559
* Unit tests for the {@link SqlGenerator}.
@@ -64,6 +68,7 @@
6468
* @author Myeonghyeon Lee
6569
* @author Mikhail Polivakha
6670
* @author Chirag Tailor
71+
* @author Diego Krupitza
6772
*/
6873
class SqlGeneratorUnitTests {
6974

@@ -250,7 +255,8 @@ void findAllSortedWithNullHandling_resolvesNullHandlingWhenDialectSupportsIt() {
250255

251256
SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class, PostgresDialect.INSTANCE);
252257

253-
String sql = sqlGenerator.getFindAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "name", Sort.NullHandling.NULLS_LAST)));
258+
String sql = sqlGenerator
259+
.getFindAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "name", Sort.NullHandling.NULLS_LAST)));
254260

255261
assertThat(sql).contains("ORDER BY \"dummy_entity\".\"x_name\" ASC NULLS LAST");
256262
}
@@ -260,7 +266,8 @@ void findAllSortedWithNullHandling_ignoresNullHandlingWhenDialectDoesNotSupportI
260266

261267
SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class, SqlServerDialect.INSTANCE);
262268

263-
String sql = sqlGenerator.getFindAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "name", Sort.NullHandling.NULLS_LAST)));
269+
String sql = sqlGenerator
270+
.getFindAll(Sort.by(new Sort.Order(Sort.Direction.ASC, "name", Sort.NullHandling.NULLS_LAST)));
264271

265272
assertThat(sql).endsWith("ORDER BY dummy_entity.x_name ASC");
266273
}
@@ -716,6 +723,25 @@ void columnForReferencedEntityWithoutId() {
716723
SqlIdentifier.quoted("child"), SqlIdentifier.quoted("CHILD_PARENT_OF_NO_ID_CHILD"));
717724
}
718725

726+
@Test
727+
void selectByQueryValidTest() {
728+
final SqlGenerator sqlGenerator = createSqlGenerator(DummyEntity.class);
729+
730+
DummyEntity probe = new DummyEntity();
731+
probe.name = "Diego";
732+
733+
Criteria criteria = Criteria.where("name").is(probe.name);
734+
Query query = Query.query(criteria);
735+
736+
MapSqlParameterSource parameterSource = new MapSqlParameterSource();
737+
738+
String generatedSQL = sqlGenerator.selectByQuery(query, parameterSource);
739+
assertThat(generatedSQL).isNotNull().contains(":x_name");
740+
741+
assertThat(parameterSource.getValues()) //
742+
.containsOnly(entry("x_name", probe.name));
743+
}
744+
719745
private SqlIdentifier getAlias(Object maybeAliased) {
720746

721747
if (maybeAliased instanceof Aliased) {
@@ -737,7 +763,8 @@ private PersistentPropertyPath<RelationalPersistentProperty> getPath(String path
737763
@SuppressWarnings("unused")
738764
static class DummyEntity {
739765

740-
@Column("id1") @Id Long id;
766+
@Column("id1")
767+
@Id Long id;
741768
String name;
742769
ReferencedEntity ref;
743770
Set<Element> elements;
@@ -810,7 +837,8 @@ static class EntityWithQuotedColumnName {
810837

811838
// these column names behave like single double quote in the name since the get quoted and then doubling the double
812839
// quote escapes it.
813-
@Id @Column("test\"\"_@id") Long id;
840+
@Id
841+
@Column("test\"\"_@id") Long id;
814842
@Column("test\"\"_@123") String name;
815843
}
816844

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

+62-1
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@ void findOneByExampleMultipleMatchShouldGetOne() {
611611
DummyEntity dummyEntity2 = createDummyEntity();
612612
repository.save(dummyEntity2);
613613

614-
Example<DummyEntity> example = Example.of(new DummyEntity());
614+
Example<DummyEntity> example = Example.of(createDummyEntity());
615615

616616
assertThatThrownBy(() -> repository.findOne(example)).isInstanceOf(IncorrectResultSizeDataAccessException.class)
617617
.hasMessageContaining("expected 1, actual 2");
@@ -632,6 +632,67 @@ void findOneByExampleShouldGetNone() {
632632
assertThat(foundExampleDiego).isNotPresent();
633633
}
634634

635+
@Test
636+
void findAllByExampleShouldGetOne() {
637+
638+
DummyEntity dummyEntity1 = createDummyEntity();
639+
dummyEntity1.setFlag(true);
640+
641+
repository.save(dummyEntity1);
642+
643+
DummyEntity dummyEntity2 = createDummyEntity();
644+
dummyEntity2.setName("Diego");
645+
646+
repository.save(dummyEntity2);
647+
648+
Example<DummyEntity> example = Example.of(new DummyEntity("Diego"));
649+
650+
Iterable<DummyEntity> allFound = repository.findAll(example);
651+
652+
assertThat(allFound) //
653+
.isNotNull() //
654+
.hasSize(1) //
655+
.extracting(DummyEntity::getName) //
656+
.containsExactly(example.getProbe().getName());
657+
}
658+
659+
@Test
660+
void findAllByExampleMultipleMatchShouldGetOne() {
661+
662+
DummyEntity dummyEntity1 = createDummyEntity();
663+
repository.save(dummyEntity1);
664+
665+
DummyEntity dummyEntity2 = createDummyEntity();
666+
repository.save(dummyEntity2);
667+
668+
Example<DummyEntity> example = Example.of(createDummyEntity());
669+
670+
Iterable<DummyEntity> allFound = repository.findAll(example);
671+
672+
assertThat(allFound) //
673+
.isNotNull() //
674+
.hasSize(2) //
675+
.extracting(DummyEntity::getName) //
676+
.containsOnly(example.getProbe().getName());
677+
}
678+
679+
@Test
680+
void findAllByExampleShouldGetNone() {
681+
682+
DummyEntity dummyEntity1 = createDummyEntity();
683+
dummyEntity1.setFlag(true);
684+
685+
repository.save(dummyEntity1);
686+
687+
Example<DummyEntity> example = Example.of(new DummyEntity("NotExisting"));
688+
689+
Iterable<DummyEntity> allFound = repository.findAll(example);
690+
691+
assertThat(allFound) //
692+
.isNotNull() //
693+
.isEmpty();
694+
}
695+
635696
private Instant createDummyBeforeAndAfterNow() {
636697

637698
Instant now = Instant.now();

0 commit comments

Comments
 (0)