Browse Source

Merge 31ab0cbfc2 into f4201529bc

pull/3439/merge
Gichan Im 5 days ago committed by GitHub
parent
commit
f496b296c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 6
      src/main/java/org/springframework/data/repository/support/DefaultRepositoryInvokerFactory.java
  2. 104
      src/main/java/org/springframework/data/repository/support/ReactiveCrudRepositoryInvoker.java
  3. 138
      src/test/java/org/springframework/data/repository/support/ReactiveCrudRepositoryInvokerUnitTests.java

6
src/main/java/org/springframework/data/repository/support/DefaultRepositoryInvokerFactory.java

@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; @@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.util.Assert;
@ -95,7 +96,10 @@ public class DefaultRepositoryInvokerFactory implements RepositoryInvokerFactory @@ -95,7 +96,10 @@ public class DefaultRepositoryInvokerFactory implements RepositoryInvokerFactory
@SuppressWarnings("unchecked")
protected RepositoryInvoker createInvoker(RepositoryInformation information, Object repository) {
if (repository instanceof PagingAndSortingRepository && repository instanceof CrudRepository) {
if (repository instanceof ReactiveCrudRepository) {
return new ReactiveCrudRepositoryInvoker((ReactiveCrudRepository<Object, Object>) repository, information,
conversionService);
} else if (repository instanceof PagingAndSortingRepository && repository instanceof CrudRepository) {
return new PagingAndSortingRepositoryInvoker((PagingAndSortingRepository<Object, Object>) repository, information,
conversionService);
} else if (repository instanceof CrudRepository) {

104
src/main/java/org/springframework/data/repository/support/ReactiveCrudRepositoryInvoker.java

@ -0,0 +1,104 @@ @@ -0,0 +1,104 @@
/*
* Copyright 2026-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.support;
import java.lang.reflect.Method;
import java.util.Optional;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.core.CrudMethods;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
/**
* {@link RepositoryInvoker} to shortcut execution of CRUD methods into direct calls on a
* {@link ReactiveCrudRepository}. This invoker blocks on reactive types to provide synchronous
* execution semantics required by the repository populator infrastructure.
*
* @author Gichan Im
* @since 4.1
* @see ReactiveCrudRepository
*/
class ReactiveCrudRepositoryInvoker extends ReflectionRepositoryInvoker {
private final ReactiveCrudRepository<Object, Object> repository;
private final boolean customSaveMethod;
private final boolean customFindOneMethod;
private final boolean customFindAllMethod;
private final boolean customDeleteMethod;
/**
* Creates a new {@link ReactiveCrudRepositoryInvoker} for the given {@link ReactiveCrudRepository},
* {@link RepositoryMetadata} and {@link ConversionService}.
*
* @param repository must not be {@literal null}.
* @param metadata must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public ReactiveCrudRepositoryInvoker(ReactiveCrudRepository<Object, Object> repository, RepositoryMetadata metadata,
ConversionService conversionService) {
super(repository, metadata, conversionService);
CrudMethods crudMethods = metadata.getCrudMethods();
this.customSaveMethod = isRedeclaredMethod(crudMethods.getSaveMethod());
this.customFindOneMethod = isRedeclaredMethod(crudMethods.getFindOneMethod());
this.customDeleteMethod = isRedeclaredMethod(crudMethods.getDeleteMethod());
this.customFindAllMethod = isRedeclaredMethod(crudMethods.getFindAllMethod());
this.repository = repository;
}
@Override
public Iterable<Object> invokeFindAll(Sort sort) {
return customFindAllMethod ? super.invokeFindAll(sort) : repository.findAll().collectList().block();
}
@Override
public Iterable<Object> invokeFindAll(Pageable pageable) {
return customFindAllMethod ? super.invokeFindAll(pageable) : repository.findAll().collectList().block();
}
@Override
@SuppressWarnings("unchecked")
public <T> Optional<T> invokeFindById(Object id) {
return customFindOneMethod ? super.invokeFindById(id)
: (Optional<T>) repository.findById(convertId(id)).blockOptional();
}
@Override
@SuppressWarnings("unchecked")
public <T> T invokeSave(T entity) {
return customSaveMethod ? super.invokeSave(entity) : (T) repository.save(entity).block();
}
@Override
public void invokeDeleteById(Object id) {
if (customDeleteMethod) {
super.invokeDeleteById(id);
} else {
repository.deleteById(convertId(id)).block();
}
}
private static boolean isRedeclaredMethod(Optional<Method> method) {
return method.map(it -> !it.getDeclaringClass().equals(ReactiveCrudRepository.class)).orElse(false);
}
}

138
src/test/java/org/springframework/data/repository/support/ReactiveCrudRepositoryInvokerUnitTests.java

@ -0,0 +1,138 @@ @@ -0,0 +1,138 @@
/*
* Copyright 2026-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.support;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.repository.support.RepositoryInvocationTestUtils.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.format.support.DefaultFormattingConversionService;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* Unit tests for {@link ReactiveCrudRepositoryInvoker}.
*
* @author Gichan Im
*/
@ExtendWith(MockitoExtension.class)
class ReactiveCrudRepositoryInvokerUnitTests {
@Mock ReactivePersonRepository repository;
@Mock ReactiveOrderRepository orderRepository;
@Test // GH-2090
void invokesRedeclaredSave() {
when(orderRepository.save(any())).thenReturn(Mono.just(new Order()));
getInvokerFor(orderRepository, expectInvocationOnType(ReactiveOrderRepository.class)).invokeSave(new Order());
}
@Test // GH-2090
void invokesRedeclaredFindById() {
when(orderRepository.findById(any(Long.class))).thenReturn(Mono.empty());
getInvokerFor(orderRepository, expectInvocationOnType(ReactiveOrderRepository.class)).invokeFindById(1L);
}
@Test // GH-2090
void invokesRedeclaredDelete() {
when(orderRepository.deleteById(any(Long.class))).thenReturn(Mono.empty());
getInvokerFor(orderRepository, expectInvocationOnType(ReactiveOrderRepository.class)).invokeDeleteById(1L);
}
@Test // GH-2090
void invokesSaveOnReactiveCrudRepository() throws Exception {
when(repository.save(any())).thenReturn(Mono.just(new Person()));
var method = ReactiveCrudRepository.class.getMethod("save", Object.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeSave(new Person());
}
@Test // GH-2090
void invokesFindByIdOnReactiveCrudRepository() throws Exception {
when(repository.findById(any(Long.class))).thenReturn(Mono.empty());
var method = ReactiveCrudRepository.class.getMethod("findById", Object.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindById(1L);
}
@Test // GH-2090
void invokesDeleteByIdOnReactiveCrudRepository() throws Exception {
when(repository.deleteById(any(Long.class))).thenReturn(Mono.empty());
var method = ReactiveCrudRepository.class.getMethod("deleteById", Object.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeDeleteById(1L);
}
@Test // GH-2090
void invokesFindAllOnReactiveCrudRepository() throws Exception {
when(repository.findAll()).thenReturn(Flux.empty());
var method = ReactiveCrudRepository.class.getMethod("findAll");
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Pageable.unpaged());
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Sort.unsorted());
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static RepositoryInvoker getInvokerFor(Object repository, VerifyingMethodInterceptor interceptor) {
var proxy = getVerifyingRepositoryProxy(repository, interceptor);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(repository.getClass().getInterfaces()[0]);
GenericConversionService conversionService = new DefaultFormattingConversionService();
return new ReactiveCrudRepositoryInvoker((ReactiveCrudRepository) proxy, metadata, conversionService);
}
static class Person {}
static class Order {}
interface ReactivePersonRepository extends ReactiveCrudRepository<Person, Long> {}
interface ReactiveOrderRepository extends ReactiveCrudRepository<Order, Long> {
@Override
<S extends Order> Mono<S> save(S entity);
@Override
Mono<Order> findById(Long id);
@Override
Mono<Void> deleteById(Long id);
}
}
Loading…
Cancel
Save