diff --git a/pom.xml b/pom.xml
index f0f583b51e..ad5120a38f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.springframework.data
spring-data-commons
- 2.5.0-SNAPSHOT
+ 2.5.0-GH-2341-SNAPSHOT
Spring Data Core
@@ -339,7 +339,7 @@
0.1.4
test
-
+
org.jmolecules.integrations
jmolecules-spring
diff --git a/src/main/java/org/springframework/data/repository/core/RepositoryCreationException.java b/src/main/java/org/springframework/data/repository/core/RepositoryCreationException.java
new file mode 100644
index 0000000000..4322ff0d76
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/core/RepositoryCreationException.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 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.core;
+
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+
+/**
+ * Exception thrown in the context of repository creation.
+ *
+ * @author Mark Paluch
+ * @since 2.5
+ */
+@SuppressWarnings("serial")
+public class RepositoryCreationException extends InvalidDataAccessApiUsageException {
+
+ private final Class> repositoryInterface;
+
+ /**
+ * Constructor for RepositoryCreationException.
+ *
+ * @param msg the detail message.
+ * @param repositoryInterface the repository interface.
+ */
+ public RepositoryCreationException(String msg, Class> repositoryInterface) {
+ super(msg);
+ this.repositoryInterface = repositoryInterface;
+ }
+
+ /**
+ * Constructor for RepositoryException.
+ *
+ * @param msg the detail message.
+ * @param cause the root cause from the data access API in use.
+ * @param repositoryInterface the repository interface.
+ */
+ public RepositoryCreationException(String msg, Throwable cause, Class> repositoryInterface) {
+ super(msg, cause);
+ this.repositoryInterface = repositoryInterface;
+ }
+
+ public Class> getRepositoryInterface() {
+ return repositoryInterface;
+ }
+}
diff --git a/src/main/java/org/springframework/data/repository/core/support/FragmentNotImplementedException.java b/src/main/java/org/springframework/data/repository/core/support/FragmentNotImplementedException.java
new file mode 100644
index 0000000000..5abf6fb8b4
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/core/support/FragmentNotImplementedException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 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.core.support;
+
+import org.springframework.data.repository.core.RepositoryCreationException;
+
+/**
+ * Exception thrown during repository creation or repository method invocation when invoking a repository method on a
+ * fragment without an implementation.
+ *
+ * @author Mark Paluch
+ * @since 2.5
+ */
+@SuppressWarnings("serial")
+public class FragmentNotImplementedException extends RepositoryCreationException {
+
+ private final RepositoryFragment> fragment;
+
+ /**
+ * Constructor for FragmentNotImplementedException.
+ *
+ * @param msg the detail message.
+ * @param repositoryInterface the repository interface.
+ * @param fragment the offending repository fragment.
+ */
+ public FragmentNotImplementedException(String msg, Class> repositoryInterface, RepositoryFragment> fragment) {
+ super(msg, repositoryInterface);
+ this.fragment = fragment;
+ }
+
+ public RepositoryFragment> getFragment() {
+ return fragment;
+ }
+}
diff --git a/src/main/java/org/springframework/data/repository/core/support/IncompleteRepositoryCompositionException.java b/src/main/java/org/springframework/data/repository/core/support/IncompleteRepositoryCompositionException.java
new file mode 100644
index 0000000000..a780c0f34f
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/core/support/IncompleteRepositoryCompositionException.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 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.core.support;
+
+import org.springframework.data.repository.core.RepositoryCreationException;
+
+/**
+ * Exception thrown during repository creation when a the repository has custom methods that are not backed by a
+ * fragment or if no fragment could be found for a repository method invocation.
+ *
+ * @author Mark Paluch
+ * @since 2.5
+ */
+@SuppressWarnings("serial")
+public class IncompleteRepositoryCompositionException extends RepositoryCreationException {
+
+ /**
+ * Constructor for IncompleteRepositoryCompositionException.
+ *
+ * @param msg the detail message.
+ * @param repositoryInterface the repository interface.
+ */
+ public IncompleteRepositoryCompositionException(String msg, Class> repositoryInterface) {
+ super(msg, repositoryInterface);
+ }
+}
diff --git a/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java b/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java
index 979b0f8e5f..c06a5e5652 100644
--- a/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java
+++ b/src/main/java/org/springframework/data/repository/core/support/QueryExecutorMethodInterceptor.java
@@ -29,6 +29,7 @@
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.DefaultRepositoryInvocationMulticaster;
import org.springframework.data.repository.core.support.RepositoryInvocationMulticaster.NoOpRepositoryInvocationMulticaster;
+import org.springframework.data.repository.query.QueryCreationException;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;
@@ -97,7 +98,13 @@ private Map mapMethodsToQuery(RepositoryInformation rep
private Pair lookupQuery(Method method, RepositoryInformation information,
QueryLookupStrategy strategy, ProjectionFactory projectionFactory) {
- return Pair.of(method, strategy.resolveQuery(method, information, projectionFactory, namedQueries));
+ try {
+ return Pair.of(method, strategy.resolveQuery(method, information, projectionFactory, namedQueries));
+ } catch (QueryCreationException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ throw QueryCreationException.create(e.getMessage(), e, information.getRepositoryInterface(), method);
+ }
}
@SuppressWarnings({ "rawtypes", "unchecked" })
diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java
index 388791a70c..6c3a6a98a8 100644
--- a/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java
+++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java
@@ -316,8 +316,12 @@ Method getMethod(Method method) {
public void validateImplementation() {
fragments.stream().forEach(it -> it.getImplementation() //
- .orElseThrow(() -> new IllegalStateException(String.format("Fragment %s has no implementation.",
- ClassUtils.getQualifiedName(it.getSignatureContributor())))));
+ .orElseThrow(() -> {
+ Class> repositoryInterface = metadata != null ? metadata.getRepositoryInterface() : Object.class;
+ return new FragmentNotImplementedException(String.format("Fragment %s used in %s has no implementation.",
+ ClassUtils.getQualifiedName(it.getSignatureContributor()),
+ ClassUtils.getQualifiedName(repositoryInterface)), repositoryInterface, it);
+ }));
}
/*
diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java
index 96768c5796..c9d761e8ca 100644
--- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java
+++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java
@@ -19,6 +19,7 @@
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -28,6 +29,7 @@
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.interceptor.ExposeInvocationInterceptor;
import org.springframework.beans.BeanUtils;
@@ -57,12 +59,12 @@
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
-import org.springframework.data.repository.util.ClassUtils;
import org.springframework.data.repository.util.QueryExecutionConverters;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.lang.Nullable;
import org.springframework.transaction.interceptor.TransactionalProxy;
import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
import org.springframework.util.ObjectUtils;
@@ -312,8 +314,6 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra
repositoryCompositionStep.end();
- validate(information, composition);
-
StartupStep repositoryTargetStep = onEvent(applicationStartup, "spring.data.repository.target",
repositoryInterface);
Object target = getTargetRepository(information);
@@ -321,6 +321,9 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra
repositoryTargetStep.tag("target", target.getClass().getName());
repositoryTargetStep.end();
+ RepositoryComposition compositionToUse = composition.append(RepositoryFragment.implemented(target));
+ validate(information, compositionToUse);
+
// Create proxy
StartupStep repositoryProxyStep = onEvent(applicationStartup, "spring.data.repository.proxy", repositoryInterface);
ProxyFactory result = new ProxyFactory();
@@ -357,7 +360,6 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra
result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy,
namedQueries, queryPostProcessors, methodInvocationListeners));
- RepositoryComposition compositionToUse = composition.append(RepositoryFragment.implemented(target));
result.addAdvice(
new ImplementationMethodExecutionInterceptor(information, compositionToUse, methodInvocationListeners));
@@ -502,17 +504,7 @@ protected Optional getQueryLookupStrategy(@Nullable Key key
*/
private void validate(RepositoryInformation repositoryInformation, RepositoryComposition composition) {
- if (repositoryInformation.hasCustomMethod()) {
-
- if (composition.isEmpty()) {
-
- throw new IllegalArgumentException(
- String.format("You have custom methods in %s but have not provided a custom implementation!",
- repositoryInformation.getRepositoryInterface()));
- }
-
- composition.validateImplementation();
- }
+ RepositoryValidator.validate(composition, getClass(), repositoryInformation);
validate(repositoryInformation);
}
@@ -606,7 +598,7 @@ public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) thro
try {
return composition.invoke(invocationMulticaster, method, arguments);
} catch (Exception e) {
- ClassUtils.unwrapReflectionException(e);
+ org.springframework.util.ReflectionUtils.handleReflectionException(e);
}
throw new IllegalStateException("Should not occur!");
@@ -715,4 +707,94 @@ public String toString() {
+ this.getRepositoryInterfaceName() + ", compositionHash=" + this.getCompositionHash() + ")";
}
}
+
+ /**
+ * Validator utility to catch common mismatches with a proper error message instead of letting the query mechanism
+ * attempt implementing a query method and fail with a less specific message.
+ */
+ static class RepositoryValidator {
+
+ static Map, String> WELL_KNOWN_EXECUTORS = new HashMap<>();
+
+ static {
+
+ org.springframework.data.repository.util.ClassUtils.ifPresent(
+ "org.springframework.data.querydsl.QuerydslPredicateExecutor", RepositoryValidator.class.getClassLoader(),
+ it -> {
+ WELL_KNOWN_EXECUTORS.put(it, "Querydsl");
+ });
+
+ org.springframework.data.repository.util.ClassUtils.ifPresent(
+ "org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor",
+ RepositoryValidator.class.getClassLoader(), it -> {
+ WELL_KNOWN_EXECUTORS.put(it, "Reactive Querydsl");
+ });
+
+ org.springframework.data.repository.util.ClassUtils.ifPresent(
+ "org.springframework.data.repository.query.QueryByExampleExecutor",
+ RepositoryValidator.class.getClassLoader(), it -> {
+ WELL_KNOWN_EXECUTORS.put(it, "Query by Example");
+ });
+
+ org.springframework.data.repository.util.ClassUtils.ifPresent(
+ "org.springframework.data.repository.query.ReactiveQueryByExampleExecutor",
+ RepositoryValidator.class.getClassLoader(), it -> {
+ WELL_KNOWN_EXECUTORS.put(it, "Reactive Query by Example");
+ });
+ }
+
+ /**
+ * Validate the {@link RepositoryComposition} for custom implementations and well-known executors.
+ *
+ * @param composition
+ * @param source
+ * @param repositoryInformation
+ */
+ public static void validate(RepositoryComposition composition, Class> source,
+ RepositoryInformation repositoryInformation) {
+
+ Class> repositoryInterface = repositoryInformation.getRepositoryInterface();
+ if (repositoryInformation.hasCustomMethod()) {
+
+ if (composition.isEmpty()) {
+
+ throw new IncompleteRepositoryCompositionException(
+ String.format("You have custom methods in %s but have not provided a custom implementation!",
+ org.springframework.util.ClassUtils.getQualifiedName(repositoryInterface)),
+ repositoryInterface);
+ }
+
+ composition.validateImplementation();
+ }
+
+ for (Map.Entry, String> entry : WELL_KNOWN_EXECUTORS.entrySet()) {
+
+ Class> executorInterface = entry.getKey();
+ if (!executorInterface.isAssignableFrom(repositoryInterface)) {
+ continue;
+ }
+
+ if (!containsFragmentImplementation(composition, executorInterface)) {
+ throw new UnsupportedFragmentException(
+ String.format("Repository %s implements %s but %s does not support %s!",
+ ClassUtils.getQualifiedName(repositoryInterface), ClassUtils.getQualifiedName(executorInterface),
+ ClassUtils.getShortName(source), entry.getValue()),
+ repositoryInterface, executorInterface);
+ }
+ }
+ }
+
+ private static boolean containsFragmentImplementation(RepositoryComposition composition,
+ Class> executorInterface) {
+
+ for (RepositoryFragment> fragment : composition.getFragments()) {
+
+ if (fragment.getImplementation().filter(executorInterface::isInstance).isPresent()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
}
diff --git a/src/main/java/org/springframework/data/repository/core/support/UnsupportedFragmentException.java b/src/main/java/org/springframework/data/repository/core/support/UnsupportedFragmentException.java
new file mode 100644
index 0000000000..e748d4123b
--- /dev/null
+++ b/src/main/java/org/springframework/data/repository/core/support/UnsupportedFragmentException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 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.core.support;
+
+import org.springframework.data.repository.core.RepositoryCreationException;
+
+/**
+ * Exception thrown during repository creation when a well-known fragment interface is not supported by the repository
+ * factory.
+ *
+ * @author Mark Paluch
+ * @since 2.5
+ */
+@SuppressWarnings("serial")
+public class UnsupportedFragmentException extends RepositoryCreationException {
+
+ private final Class> fragmentInterface;
+
+ /**
+ * Constructor for UnsupportedFragmentException.
+ *
+ * @param msg the detail message.
+ * @param repositoryInterface the repository interface.
+ * @param fragmentInterface the offending fragment interface.
+ */
+ public UnsupportedFragmentException(String msg, Class> repositoryInterface, Class> fragmentInterface) {
+ super(msg, repositoryInterface);
+ this.fragmentInterface = fragmentInterface;
+ }
+
+ public Class> getFragmentInterface() {
+ return fragmentInterface;
+ }
+}
diff --git a/src/main/java/org/springframework/data/repository/query/QueryCreationException.java b/src/main/java/org/springframework/data/repository/query/QueryCreationException.java
index 468c91da73..f17353687f 100644
--- a/src/main/java/org/springframework/data/repository/query/QueryCreationException.java
+++ b/src/main/java/org/springframework/data/repository/query/QueryCreationException.java
@@ -15,24 +15,39 @@
*/
package org.springframework.data.repository.query;
+import java.lang.reflect.Method;
+
+import org.springframework.data.repository.core.RepositoryCreationException;
+
/**
- * Exception to be thrown if a query cannot be created from a {@link QueryMethod}.
+ * Exception to be thrown if a query cannot be created from a {@link Method}.
*
* @author Oliver Gierke
+ * @author Mark Paluch
*/
-public final class QueryCreationException extends RuntimeException {
+public final class QueryCreationException extends RepositoryCreationException {
private static final long serialVersionUID = -1238456123580L;
private static final String MESSAGE_TEMPLATE = "Could not create query for method %s! Could not find property %s on domain class %s.";
+ private final Method method;
+
+ /**
+ * Creates a new {@link QueryCreationException}.
+ */
+ private QueryCreationException(String message, QueryMethod method) {
+
+ super(message, method.getMetadata().getRepositoryInterface());
+ this.method = method.getMethod();
+ }
+
/**
* Creates a new {@link QueryCreationException}.
- *
- * @param method
*/
- private QueryCreationException(String message) {
+ private QueryCreationException(String message, Throwable cause, Class> repositoryInterface, Method method) {
- super(message);
+ super(message, cause, repositoryInterface);
+ this.method = method;
}
/**
@@ -45,7 +60,7 @@ private QueryCreationException(String message) {
public static QueryCreationException invalidProperty(QueryMethod method, String propertyName) {
return new QueryCreationException(String.format(MESSAGE_TEMPLATE, method, propertyName, method.getDomainClass()
- .getName()));
+ .getName()), method);
}
/**
@@ -57,7 +72,8 @@ public static QueryCreationException invalidProperty(QueryMethod method, String
*/
public static QueryCreationException create(QueryMethod method, String message) {
- return new QueryCreationException(String.format("Could not create query for %s! Reason: %s", method, message));
+ return new QueryCreationException(String.format("Could not create query for %s! Reason: %s", method, message),
+ method);
}
/**
@@ -68,7 +84,29 @@ public static QueryCreationException create(QueryMethod method, String message)
* @return
*/
public static QueryCreationException create(QueryMethod method, Throwable cause) {
+ return new QueryCreationException(cause.getMessage(), cause, method.getMetadata().getRepositoryInterface(),
+ method.getMethod());
+ }
- return create(method, cause.getMessage());
+ /**
+ * Creates a new {@link QueryCreationException} for the given {@link QueryMethod} and {@link Throwable} as cause.
+ *
+ * @param method
+ * @param cause
+ * @return
+ * @since 2.5
+ */
+ public static QueryCreationException create(String message, Throwable cause, Class> repositoryInterface,
+ Method method) {
+ return new QueryCreationException(String.format("Could not create query for %s! Reason: %s", method, message),
+ cause, repositoryInterface, method);
+ }
+
+ /**
+ * @return
+ * @since 2.5
+ */
+ public Method getMethod() {
+ return method;
}
}
diff --git a/src/main/java/org/springframework/data/repository/query/QueryMethod.java b/src/main/java/org/springframework/data/repository/query/QueryMethod.java
index 496cc133dc..4ce0d25283 100644
--- a/src/main/java/org/springframework/data/repository/query/QueryMethod.java
+++ b/src/main/java/org/springframework/data/repository/query/QueryMethod.java
@@ -241,6 +241,14 @@ public ResultProcessor getResultProcessor() {
return resultProcessor;
}
+ RepositoryMetadata getMetadata() {
+ return metadata;
+ }
+
+ Method getMethod() {
+ return method;
+ }
+
/*
* (non-Javadoc)
* @see java.lang.Object#toString()
diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryCompositionUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryCompositionUnitTests.java
index cdc6e904bb..08c561eddc 100644
--- a/src/test/java/org/springframework/data/repository/core/support/RepositoryCompositionUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryCompositionUnitTests.java
@@ -136,16 +136,17 @@ void shouldCallMethodsInOrder() throws Throwable {
assertThat(barFoo.invoke(barFoo.findMethod(getString).get())).isEqualTo("bar");
}
- @Test // DATACMNS-102
+ @Test // DATACMNS-102, GH-2341
void shouldValidateStructuralFragments() {
RepositoryComposition mixed = RepositoryComposition.of(RepositoryFragment.structural(QueryByExampleExecutor.class),
RepositoryFragment.implemented(backingRepo));
- assertThatIllegalStateException() //
+ assertThatExceptionOfType(FragmentNotImplementedException.class) //
.isThrownBy(mixed::validateImplementation) //
.withMessageContaining(
- "Fragment org.springframework.data.repository.query.QueryByExampleExecutor has no implementation.");
+ "Fragment org.springframework.data.repository.query.QueryByExampleExecutor")
+ .withMessageContaining("has no implementation");
}
@Test // DATACMNS-102
diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java
index 3eeb54316d..f4e547805f 100755
--- a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java
@@ -51,6 +51,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.projection.ProjectionFactory;
+import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.RepositoryDefinition;
@@ -61,7 +62,13 @@
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocation;
import org.springframework.data.repository.core.support.RepositoryMethodInvocationListener.RepositoryMethodInvocationResult.State;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+import org.springframework.data.repository.query.QueryCreationException;
+import org.springframework.data.repository.query.QueryLookupStrategy;
+import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
+import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
import org.springframework.data.repository.query.RepositoryQuery;
+import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.data.repository.sample.User;
import org.springframework.lang.Nullable;
import org.springframework.scheduling.annotation.Async;
@@ -422,6 +429,49 @@ void callsApplicationStartupOnRepositoryInitialization() {
orderedInvocation.verify(startup).start("spring.data.repository.proxy");
}
+ @Test // GH-2341
+ void dummyRepositoryShouldsupportQuerydsl() {
+ factory.getRepository(WithQuerydsl.class, backingRepo);
+ }
+
+ @Test // GH-2341
+ void dummyRepositoryNotSupportingReactiveQuerydslShouldRaiseException() {
+ assertThatThrownBy(() -> factory.getRepository(WithReactiveQuerydsl.class, backingRepo))
+ .isInstanceOf(UnsupportedFragmentException.class).hasMessage(
+ "Repository org.springframework.data.repository.core.support.RepositoryFactorySupportUnitTests$WithReactiveQuerydsl implements org.springframework.data.repository.query.ReactiveQueryByExampleExecutor but DummyRepositoryFactory does not support Reactive Query by Example!");
+ }
+
+ @Test // GH-2341
+ void dummyRepositoryNotSupportingQbeShouldRaiseException() {
+ assertThatThrownBy(() -> factory.getRepository(WithQbe.class, backingRepo))
+ .hasMessageContaining("does not support Query by Example");
+ }
+
+ @Test // GH-2341
+ void dummyRepositoryNotSupportingReactiveQbeShouldRaiseException() {
+ assertThatThrownBy(() -> factory.getRepository(WithReactiveQbe.class, backingRepo))
+ .hasMessageContaining("does not support Reactive Query by Example");
+ }
+
+ @Test // GH-2341
+ void derivedQueryMethodCannotBeImplemented() {
+
+ DummyRepositoryFactory factory = new DummyRepositoryFactory(backingRepo) {
+ @Override
+ protected Optional getQueryLookupStrategy(QueryLookupStrategy.Key key,
+ QueryMethodEvaluationContextProvider evaluationContextProvider) {
+ return Optional.of((method, metadata, factory, namedQueries) -> {
+ new PartTree(method.getName(), method.getReturnType());
+ return null;
+ });
+ }
+ };
+
+ assertThatThrownBy(() -> factory.getRepository(WithQueryMethodUsingInvalidProperty.class))
+ .isInstanceOf(QueryCreationException.class).hasMessageContaining("findAllByName")
+ .hasMessageContaining("No property name found for type Object");
+ }
+
private ConvertingRepository prepareConvertingRepository(final Object expectedValue) {
when(factory.queryOne.execute(any(Object[].class))).then(invocation -> {
@@ -543,4 +593,27 @@ static class CustomRepositoryBaseClass {
CustomRepositoryBaseClass(EntityInformation, ?> information) {}
}
+
+ interface WithQuerydsl extends Repository