#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:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user