#344 - Add support for support distinct derived query methods.

We now support distinct derived queries that are useful with projections to retrieve distinct results.

interface UserRepository extends Repository<User, Long> {

  Mono<UserProjection> findDistinctByFirstName(String firstName);

}

interface UserProjection {

  String getFirstName();

}

Original pull request: #346.
This commit is contained in:
Mingyuan Wu
2020-04-21 15:12:37 +02:00
committed by Mark Paluch
parent ea464b2d37
commit dd7c713980
4 changed files with 56 additions and 15 deletions

View File

@@ -41,6 +41,7 @@ import org.springframework.util.Assert;
*
* @author Mark Paluch
* @author Roman Chigvintsev
* @author Mingyuan Wu
*/
class DefaultStatementMapper implements StatementMapper {
@@ -84,8 +85,11 @@ class DefaultStatementMapper implements StatementMapper {
@Nullable RelationalPersistentEntity<?> entity) {
Table table = selectSpec.getTable();
SelectBuilder.SelectFromAndJoin selectBuilder = StatementBuilder.select(getSelectList(selectSpec, entity))
.from(table);
SelectBuilder.SelectAndFrom selectAndFrom = StatementBuilder.select(getSelectList(selectSpec, entity));
if(selectSpec.isDistinct()){
selectAndFrom = selectAndFrom.distinct();
}
SelectBuilder.SelectFromAndJoin selectBuilder = selectAndFrom.from(table);
BindMarkers bindMarkers = this.dialect.getBindMarkersFactory().create();
Bindings bindings = Bindings.empty();

View File

@@ -202,9 +202,10 @@ public interface StatementMapper {
private final Sort sort;
private final long offset;
private final int limit;
private boolean distinct = false;
protected SelectSpec(Table table, List<String> projectedFields, List<Expression> selectList,
@Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset) {
@Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset, boolean distinct) {
this.table = table;
this.projectedFields = projectedFields;
this.selectList = selectList;
@@ -212,6 +213,7 @@ public interface StatementMapper {
this.sort = sort;
this.offset = offset;
this.limit = limit;
this.distinct = distinct;
}
/**
@@ -236,7 +238,7 @@ public interface StatementMapper {
List<String> projectedFields = Collections.emptyList();
List<Expression> selectList = Collections.emptyList();
return new SelectSpec(Table.create(table), projectedFields, selectList, Criteria.empty(), Sort.unsorted(), -1,
-1);
-1, false);
}
public SelectSpec doWithTable(BiFunction<Table, SelectSpec, SelectSpec> function) {
@@ -277,7 +279,7 @@ public interface StatementMapper {
List<Expression> selectList = new ArrayList<>(this.selectList);
selectList.addAll(Arrays.asList(expressions));
return new SelectSpec(this.table, projectedFields, selectList, this.criteria, this.sort, this.limit, this.offset);
return new SelectSpec(this.table, projectedFields, selectList, this.criteria, this.sort, this.limit, this.offset, this.distinct);
}
/**
@@ -293,7 +295,7 @@ public interface StatementMapper {
selectList.addAll(projectedFields);
return new SelectSpec(this.table, this.projectedFields, selectList, this.criteria, this.sort, this.limit,
this.offset);
this.offset, this.distinct);
}
/**
@@ -304,7 +306,7 @@ public interface StatementMapper {
*/
public SelectSpec withCriteria(CriteriaDefinition criteria) {
return new SelectSpec(this.table, this.projectedFields, this.selectList, criteria, this.sort, this.limit,
this.offset);
this.offset, this.distinct);
}
/**
@@ -317,11 +319,11 @@ public interface StatementMapper {
if (sort.isSorted()) {
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, sort, this.limit,
this.offset);
this.offset, this.distinct);
}
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
this.offset);
this.offset, this.distinct);
}
/**
@@ -337,33 +339,44 @@ public interface StatementMapper {
Sort sort = page.getSort();
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria,
sort.isSorted() ? sort : this.sort, page.getPageSize(), page.getOffset());
sort.isSorted() ? sort : this.sort, page.getPageSize(), page.getOffset(), this.distinct);
}
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
this.offset);
this.offset, this.distinct);
}
/**
* Associate a result offset with the select and create a new {@link SelectSpec}.
*
* @param page
* @param offset
* @return the {@link SelectSpec}.
*/
public SelectSpec offset(long offset) {
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
offset);
offset, this.distinct);
}
/**
* Associate a result limit with the select and create a new {@link SelectSpec}.
*
* @param page
* @param limit
* @return the {@link SelectSpec}.
*/
public SelectSpec limit(int limit) {
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
this.offset);
this.offset, this.distinct);
}
/**
* Associate a result statement distinct with the select and create a new {@link SelectSpec}.
*
* @param distinct
* @return the {@link SelectSpec}.
*/
public SelectSpec distinct(boolean distinct) {
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
this.offset, distinct);
}
public Table getTable() {
@@ -399,6 +412,10 @@ public interface StatementMapper {
public int getLimit() {
return this.limit;
}
public boolean isDistinct() {
return this.distinct;
}
}
/**

View File

@@ -123,6 +123,10 @@ public class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<
selectSpec = selectSpec.withSort(getSort(sort));
}
if(tree.isDistinct()){
selectSpec = selectSpec.distinct(true);
}
return statementMapper.getMappedObject(selectSpec);
}

View File

@@ -63,6 +63,7 @@ public class PartTreeR2dbcQueryUnitTests {
private static final String TABLE = "users";
private static final String ALL_FIELDS = TABLE + ".id, " + TABLE + ".first_name, " + TABLE + ".last_name, " + TABLE
+ ".date_of_birth, " + TABLE + ".age, " + TABLE + ".active";
private static final String DISTINCT = "DISTINCT";
@Mock ConnectionFactory connectionFactory;
@Mock R2dbcConverter r2dbcConverter;
@@ -605,6 +606,19 @@ public class PartTreeR2dbcQueryUnitTests {
assertThat(bindableQuery.get()).isEqualTo("DELETE FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
}
@Test // gh-344
public void createsQueryToFindAllEntitiesByStringAttributeWithDistinct() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findDistinctByFirstName", String.class);
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = r2dbcQuery.createQuery(getAccessor(queryMethod, new Object[] { "John" }));
assertThat(bindableQuery.get())
.isEqualTo("SELECT " + DISTINCT + " " + ALL_FIELDS + " FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
}
private R2dbcQueryMethod getQueryMethod(String methodName, Class<?>... parameterTypes) throws Exception {
Method method = UserRepository.class.getMethod(methodName, parameterTypes);
return new R2dbcQueryMethod(method, new DefaultRepositoryMetadata(UserRepository.class),
@@ -682,6 +696,8 @@ public class PartTreeR2dbcQueryUnitTests {
Flux<User> findTop3ByFirstName(String firstName);
Mono<User> findFirstByFirstName(String firstName);
Mono<User> findDistinctByFirstName(String firstName);
Mono<Integer> deleteByFirstName(String firstName);
}