DATACMNS-1763 - Allow registration of QueryMethod invocation listeners.

We now allow for registering RepositoryMethodInvocationListeners to notify listeners upon repository method invocations (query methods and repository fragments). Listeners are notified upon method completion reporting the repository interface, the invoked methods, duration, outcome and arguments.

Original Pull Request: #455
This commit is contained in:
Mark Paluch
2020-07-08 15:40:18 +02:00
committed by Christoph Strobl
parent d840057b0c
commit e77947f0a5
11 changed files with 654 additions and 202 deletions

View File

@@ -50,14 +50,14 @@ class QueryExecutorMethodInterceptorUnitTests {
assertThatIllegalStateException()
.isThrownBy(() -> new QueryExecutorMethodInterceptor(information, new SpelAwareProxyProjectionFactory(),
Optional.empty(), PropertiesBasedNamedQueries.EMPTY, Collections.emptyList()));
Optional.empty(), PropertiesBasedNamedQueries.EMPTY, Collections.emptyList(), Collections.emptyList()));
}
@Test // DATACMNS-1508
void skipsQueryLookupsIfQueryLookupStrategyIsNotPresent() {
new QueryExecutorMethodInterceptor(information, new SpelAwareProxyProjectionFactory(), Optional.empty(),
PropertiesBasedNamedQueries.EMPTY, Collections.emptyList());
PropertiesBasedNamedQueries.EMPTY, Collections.emptyList(), Collections.emptyList());
verify(strategy, times(0)).resolveQuery(any(), any(), any(), any());
}

View File

@@ -29,10 +29,12 @@ import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -86,6 +88,7 @@ class RepositoryFactorySupportUnitTests {
@Mock PlainQueryCreationListener otherListener;
@Mock RepositoryProxyPostProcessor repositoryPostProcessor;
@Mock RepositoryMethodInvocationListener invocationListener;
@BeforeEach
void setUp() {
@@ -118,13 +121,16 @@ class RepositoryFactorySupportUnitTests {
verify(repositoryPostProcessor, times(1)).postProcess(any(ProxyFactory.class), any(RepositoryInformation.class));
}
@Test
@Test // DATACMNS-1764
void routesCallToRedeclaredMethodIntoTarget() {
factory.addInvocationListener(invocationListener);
ObjectRepository repository = factory.getRepository(ObjectRepository.class);
repository.save(repository);
verify(backingRepo, times(1)).save(any(Object.class));
verify(invocationListener).afterInvocation(any());
}
@Test
@@ -177,7 +183,7 @@ class RepositoryFactorySupportUnitTests {
assertThat(ReflectionTestUtils.getField(factory, "classLoader")).isEqualTo(ClassUtils.getDefaultClassLoader());
}
@Test // DATACMNS-489
@Test // DATACMNS-489, DATACMNS-1764
void wrapsExecutionResultIntoFutureIfConfigured() throws Exception {
final Object reference = new Object();
@@ -187,6 +193,8 @@ class RepositoryFactorySupportUnitTests {
return reference;
});
factory.addInvocationListener(invocationListener);
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class);
AsyncAnnotationBeanPostProcessor processor = new AsyncAnnotationBeanPostProcessor();
@@ -204,13 +212,44 @@ class RepositoryFactorySupportUnitTests {
assertThat(future.get()).isEqualTo(reference);
verify(factory.queryOne, times(1)).execute(any(Object[].class));
ArgumentCaptor<RepositoryMethodInvocationListener.Invocation> captor = ArgumentCaptor
.forClass(RepositoryMethodInvocationListener.Invocation.class);
verify(invocationListener).afterInvocation(captor.capture());
RepositoryMethodInvocationListener.Invocation value = captor.getValue();
assertThat(value.getResult()).isEqualTo(reference);
}
@Test // DATACMNS-509
@Test // DATACMNS-1764, DATACMNS-1764
void capturesFailureFromInvocation() {
when(factory.queryOne.execute(any(Object[].class))).thenThrow(new IllegalStateException());
factory.addInvocationListener(invocationListener);
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class);
try {
repository.findByLastname("Foo");
fail("Missing exception");
} catch (IllegalStateException e) {}
ArgumentCaptor<RepositoryMethodInvocationListener.Invocation> captor = ArgumentCaptor
.forClass(RepositoryMethodInvocationListener.Invocation.class);
verify(invocationListener).afterInvocation(captor.capture());
RepositoryMethodInvocationListener.Invocation invocation = captor.getValue();
assertThat(invocation.getDuration(TimeUnit.NANOSECONDS)).isGreaterThan(0);
assertThat(invocation.getException()).isInstanceOf(IllegalStateException.class);
}
@Test // DATACMNS-509, DATACMNS-1764
void convertsWithSameElementType() {
List<String> names = singletonList("Dave");
factory.addInvocationListener(invocationListener);
when(factory.queryOne.execute(any(Object[].class))).thenReturn(names);
ConvertingRepository repository = factory.getRepository(ConvertingRepository.class);
@@ -218,6 +257,13 @@ class RepositoryFactorySupportUnitTests {
assertThat(result).hasSize(1);
assertThat(result.iterator().next()).isEqualTo("Dave");
ArgumentCaptor<RepositoryMethodInvocationListener.Invocation> captor = ArgumentCaptor
.forClass(RepositoryMethodInvocationListener.Invocation.class);
verify(invocationListener).afterInvocation(captor.capture());
RepositoryMethodInvocationListener.Invocation value = captor.getValue();
assertThat(value.getResult()).isEqualTo(names);
}
@Test // DATACMNS-509
@@ -461,6 +507,8 @@ class RepositoryFactorySupportUnitTests {
Set<Object> convertListToObjectSet();
Future<Object> findByLastname(String lastname);
@Async
Future<Object> findByFirstname(String firstname);

View File

@@ -25,9 +25,11 @@ import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mockito
import org.reactivestreams.Publisher
import org.springframework.data.repository.core.support.DummyReactiveRepositoryFactory
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener
import org.springframework.data.repository.reactive.ReactiveCrudRepository
import org.springframework.data.repository.sample.User
import reactor.core.publisher.Flux
@@ -45,14 +47,17 @@ class CoroutineCrudRepositoryUnitTests {
val backingRepository = mockk<ReactiveCrudRepository<User, String>>()
lateinit var factory: DummyReactiveRepositoryFactory
lateinit var coRepository: MyCoRepository
lateinit var invocationListener: RepositoryMethodInvocationListener
@BeforeEach
fun before() {
factory = DummyReactiveRepositoryFactory(backingRepository)
invocationListener = Mockito.mock(RepositoryMethodInvocationListener::class.java)
factory.addInvocationListener(invocationListener)
coRepository = factory.getRepository(MyCoRepository::class.java)
}
@Test // DATACMNS-1508
@Test // DATACMNS-1508, DATACMNS-1764
fun shouldInvokeFindAll() {
val sample = User()
@@ -64,6 +69,7 @@ class CoroutineCrudRepositoryUnitTests {
}
assertThat(result).hasSize(1).containsOnly(sample)
Mockito.verify(invocationListener).afterInvocation(Mockito.any())
}
@Test // DATACMNS-1508
@@ -176,7 +182,7 @@ class CoroutineCrudRepositoryUnitTests {
verify { backingRepository.deleteAll(any<Publisher<User>>()) }
}
@Test // DATACMNS-1508
@Test // DATACMNS-1508, DATACMNS-1764
fun shouldBridgeQueryMethod() {
val sample = User()
@@ -188,6 +194,9 @@ class CoroutineCrudRepositoryUnitTests {
}
assertThat(result).isNotNull().isEqualTo(sample)
val captor = ArgumentCaptor.forClass(RepositoryMethodInvocationListener.Invocation::class.java)
Mockito.verify(invocationListener).afterInvocation(captor.capture())
assertThat(captor.value.result).isInstanceOf(Mono::class.java)
}
@Test // DATACMNS-1508