#475 - Refine projection result mapping.

We now differentiate the result mapping based on projection type and query type. Previously, all projections used the domain type to map results first and then serve as backend for the projection proxy/DTO creation.
We now use direct result to DTO mapping for String-based queries to allow for a greater flexibility when declaring DTO result types. Interface-based projections and derived queries remain using the two-step process of result to domain type mapping and then mapping the domain type into the projection.
This commit is contained in:
Mark Paluch
2020-10-08 15:04:14 +02:00
parent c00184b130
commit b8f4e8b765
6 changed files with 134 additions and 10 deletions

View File

@@ -22,6 +22,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.Value;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@@ -142,8 +143,8 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
}).verifyComplete();
}
@Test
void shouldFindApplyingProjection() {
@Test // gh-475
void shouldFindApplyingInterfaceProjection() {
shouldInsertNewItems();
@@ -156,6 +157,20 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
}).verifyComplete();
}
@Test // gh-475
void shouldByStringQueryApplyingDtoProjection() {
shouldInsertNewItems();
repository.findAsDtoProjection() //
.map(LegoDto::getName) //
.collectList() //
.as(StepVerifier::create) //
.consumeNextWith(actual -> {
assertThat(actual).contains("SCHAUFELRADBAGGER", "FORSCHUNGSSCHIFF");
}).verifyComplete();
}
@Test // gh-344
void shouldFindApplyingDistinctProjection() {
@@ -355,6 +370,9 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
Flux<Named> findAsProjection();
@Query("SELECT name from legoset")
Flux<LegoDto> findAsDtoProjection();
Flux<Named> findDistinctBy();
Mono<LegoSet> findByManual(int manual);
@@ -400,6 +418,17 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
@Id Integer id;
}
@Value
static class LegoDto {
String name;
String unknown;
public LegoDto(String name, String unknown) {
this.name = name;
this.unknown = unknown;
}
}
interface Named {
String getName();
}

View File

@@ -37,6 +37,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.annotation.Id;
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
@@ -621,6 +622,32 @@ class PartTreeR2dbcQueryUnitTests {
+ ".foo FROM " + TABLE + " WHERE " + TABLE + ".first_name = $1");
}
@Test // gh-475
void createsQueryToFindByOpenProjection() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findOpenProjectionBy");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery);
assertThat(bindableQuery.get()).isEqualTo(
"SELECT users.id, users.first_name, users.last_name, users.date_of_birth, users.age, users.active FROM "
+ TABLE);
}
@Test // gh-475
void createsDtoProjectionQuery() throws Exception {
R2dbcQueryMethod queryMethod = getQueryMethod("findAsDtoProjectionBy");
PartTreeR2dbcQuery r2dbcQuery = new PartTreeR2dbcQuery(queryMethod, databaseClient, r2dbcConverter,
dataAccessStrategy);
BindableQuery bindableQuery = createQuery(queryMethod, r2dbcQuery);
assertThat(bindableQuery.get()).isEqualTo(
"SELECT users.id, users.first_name, users.last_name, users.date_of_birth, users.age, users.active FROM "
+ TABLE);
}
@Test // gh-363
void createsQueryForCountProjection() throws Exception {
@@ -721,6 +748,10 @@ class PartTreeR2dbcQueryUnitTests {
Mono<UserProjection> findDistinctByFirstName(String firstName);
Mono<OpenUserProjection> findOpenProjectionBy();
Mono<UserDtoProjection> findAsDtoProjectionBy();
Mono<Integer> deleteByFirstName(String firstName);
Mono<Long> countByFirstName(String firstName);
@@ -744,4 +775,18 @@ class PartTreeR2dbcQueryUnitTests {
String getFoo();
}
interface OpenUserProjection {
String getFirstName();
@Value("#firstName")
String getFoo();
}
static class UserDtoProjection {
String firstName;
String unknown;
}
}

View File

@@ -258,6 +258,22 @@ public class StringBasedR2dbcQueryUnitTests {
verifyNoMoreInteractions(bindSpec);
}
@Test // gh-475
void usesDomainTypeForInterfaceProjectionResultMapping() {
StringBasedR2dbcQuery query = getQueryMethod("findAsInterfaceProjection");
assertThat(query.resolveResultType(query.getQueryMethod().getResultProcessor())).isEqualTo(Person.class);
}
@Test // gh-475
void usesDtoTypeForDtoResultMapping() {
StringBasedR2dbcQuery query = getQueryMethod("findAsDtoProjection");
assertThat(query.resolveResultType(query.getQueryMethod().getResultProcessor())).isEqualTo(PersonDto.class);
}
private StringBasedR2dbcQuery getQueryMethod(String name, Class<?>... args) {
Method method = ReflectionUtils.findMethod(SampleRepository.class, name, args);
@@ -306,8 +322,20 @@ public class StringBasedR2dbcQueryUnitTests {
@Query("SELECT * FROM person WHERE lastname = :name")
Person queryWithEnum(MyEnum myEnum);
@Query("SELECT * FROM person")
PersonDto findAsDtoProjection();
@Query("SELECT * FROM person")
PersonProjection findAsInterfaceProjection();
}
static class PersonDto {
}
interface PersonProjection {}
static class Person {
String name;