#421 - Handle modifying query methods returning kotlin.Unit.

Added check for kotlin.Unit to AbstractR2dbcQuery#getExecutionToWrap. This case is essentially equivalent to a return type of Void, but the singleton Unit instance needs to be returned instead of discarding the result entirely.

It was also necessary to add a check to R2dbcQueryMethod#getEntityInformation, as otherwise a PersistentEntity is created for Unit, which leads to a new instance being created via reflection down the pipeline (which is probably not a thing that should happen).

Original pull request: #422.
This commit is contained in:
Stephen Cohen
2020-07-30 18:43:45 -04:00
committed by Mark Paluch
parent dfe807067d
commit 902e2d5a62
5 changed files with 92 additions and 3 deletions

View File

@@ -267,7 +267,7 @@ Mono<Integer> setFixedFirstnameFor(String firstname, String lastname);
The result of a modifying query can be:
* `Void` to discard update count and await completion.
* `Void` (or Kotlin `Unit`) to discard update count and await completion.
* `Integer` or another numeric type emitting the affected rows count.
* `Boolean` to emit whether at least one row was updated.

View File

@@ -15,11 +15,13 @@
*/
package org.springframework.data.r2dbc.repository.query;
import kotlin.Unit;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.reactivestreams.Publisher;
import org.springframework.core.KotlinDetector;
import org.springframework.data.mapping.model.EntityInstantiators;
import org.springframework.data.r2dbc.convert.R2dbcConverter;
import org.springframework.data.r2dbc.core.DatabaseClient;
@@ -41,6 +43,7 @@ import org.springframework.util.Assert;
* Base class for reactive {@link RepositoryQuery} implementations for R2DBC.
*
* @author Mark Paluch
* @author Stephen Cohen
*/
public abstract class AbstractR2dbcQuery implements RepositoryQuery {
@@ -141,6 +144,10 @@ public abstract class AbstractR2dbcQuery implements RepositoryQuery {
return (q, t, c) -> q.rowsUpdated().then();
}
if (KotlinDetector.isKotlinPresent() && Unit.class.isAssignableFrom(returnedType.getReturnedType())) {
return (q, t, c) -> q.rowsUpdated().thenReturn(Unit.INSTANCE);
}
return (q, t, c) -> q.rowsUpdated();
}

View File

@@ -17,9 +17,12 @@ package org.springframework.data.r2dbc.repository.query;
import static org.springframework.data.repository.util.ClassUtils.*;
import kotlin.Unit;
import java.lang.reflect.Method;
import java.util.Optional;
import org.springframework.core.KotlinDetector;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Page;
@@ -51,6 +54,7 @@ import org.springframework.util.ClassUtils;
* Reactive specific implementation of {@link QueryMethod}.
*
* @author Mark Paluch
* @author Stephen Cohen
*/
public class R2dbcQueryMethod extends QueryMethod {
@@ -164,7 +168,8 @@ public class R2dbcQueryMethod extends QueryMethod {
Class<?> returnedObjectType = getReturnedObjectType();
Class<?> domainClass = getDomainClass();
if (ClassUtils.isPrimitiveOrWrapper(returnedObjectType)) {
if (ClassUtils.isPrimitiveOrWrapper(returnedObjectType)
|| KotlinDetector.isKotlinPresent() && Unit.class.isAssignableFrom(returnedObjectType)) {
this.metadata = new SimpleRelationalEntityMetadata<>((Class<Object>) domainClass,
mappingContext.getRequiredPersistentEntity(domainClass));

View File

@@ -17,6 +17,8 @@ package org.springframework.data.r2dbc.repository;
import static org.assertj.core.api.Assertions.*;
import kotlin.Unit;
import io.r2dbc.spi.ConnectionFactory;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -297,6 +299,55 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
.verifyComplete();
}
@Test // gh-421
public void shouldDeleteAllAndReturnCount() {
shouldInsertNewItems();
repository.deleteAllAndReturnCount() //
.as(StepVerifier::create) //
.expectNext(2) //
.verifyComplete();
repository.findAll() //
.as(StepVerifier::create) //
.verifyComplete();
}
@Test // gh-421
public void shouldDeleteAndReturnSuccess() {
shouldInsertNewItems();
repository.deleteByManualAndReturnSuccess(12) //
.as(StepVerifier::create) //
.expectNext(true) //
.verifyComplete();
repository.findAll() //
.map(LegoSet::getManual) //
.as(StepVerifier::create) //
.expectNext(13) //
.verifyComplete();
}
@Test // gh-421
public void shouldDeleteAndReturnKotlinUnit() {
shouldInsertNewItems();
repository.deleteByManualAndReturnKotlinUnit(12) //
.as(StepVerifier::create) //
.expectNext(Unit.INSTANCE) //
.verifyComplete();
repository.findAll() //
.map(LegoSet::getManual) //
.as(StepVerifier::create) //
.expectNext(13) //
.verifyComplete();
}
private Condition<? super Object> numberOf(int expected) {
return new Condition<>(it -> {
return it instanceof Number && ((Number) it).intValue() == expected;
@@ -322,9 +373,22 @@ public abstract class AbstractR2dbcRepositoryIntegrationTests extends R2dbcInteg
Mono<Void> deleteAllBy();
@Modifying
@Query("DELETE from legoset where manual = :manual")
Mono<Void> deleteAllByManual(int manual);
@Modifying
@Query("DELETE from legoset")
Mono<Integer> deleteAllAndReturnCount();
@Modifying
@Query("DELETE from legoset where manual = :manual")
Mono<Boolean> deleteByManualAndReturnSuccess(int manual);
@Modifying
@Query("DELETE from legoset where manual = :manual")
Mono<Unit> deleteByManualAndReturnKotlinUnit(int manual);
Mono<Integer> countByNameContains(String namePart);
}

View File

@@ -17,6 +17,8 @@ package org.springframework.data.r2dbc.repository.query;
import static org.assertj.core.api.Assertions.*;
import kotlin.Unit;
import reactor.core.publisher.Mono;
import java.lang.annotation.Retention;
@@ -45,6 +47,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
* Unit test for {@link R2dbcQueryMethod}.
*
* @author Mark Paluch
* @author Stephen Cohen
*/
public class R2dbcQueryMethodUnitTests {
@@ -128,6 +131,14 @@ public class R2dbcQueryMethodUnitTests {
assertThat(method.getEntityInformation().getJavaType()).isAssignableFrom(Contact.class);
}
@Test // gh-421
public void fallsBackToRepositoryDomainTypeIfMethodReturnsKotlinUnit() throws Exception {
R2dbcQueryMethod method = queryMethod(PersonRepository.class, "deleteByFirstname", String.class);
assertThat(method.getEntityInformation().getJavaType()).isAssignableFrom(Contact.class);
}
private R2dbcQueryMethod queryMethod(Class<?> repository, String name, Class<?>... parameters) throws Exception {
Method method = repository.getMethod(name, parameters);
@@ -144,6 +155,8 @@ public class R2dbcQueryMethodUnitTests {
Mono<Slice<Contact>> findMonoSliceByLastname(String lastname, Pageable pageRequest);
void deleteByUserName(String userName);
Unit deleteByFirstname(String firstname);
}
interface SampleRepository extends Repository<Contact, Long> {