#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:
committed by
Mark Paluch
parent
4f04c87837
commit
748d6c3340
@@ -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.
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user