DELTASPIKE-421 Optional single results.
Project: http://git-wip-us.apache.org/repos/asf/deltaspike/repo Commit: http://git-wip-us.apache.org/repos/asf/deltaspike/commit/f3c00d8c Tree: http://git-wip-us.apache.org/repos/asf/deltaspike/tree/f3c00d8c Diff: http://git-wip-us.apache.org/repos/asf/deltaspike/diff/f3c00d8c Branch: refs/heads/master Commit: f3c00d8c722e357faba4b0527499d9daa5216453 Parents: feba6a7 Author: Thomas Hug <[email protected]> Authored: Fri Nov 22 14:01:37 2013 +0100 Committer: Thomas Hug <[email protected]> Committed: Thu Nov 28 14:38:25 2013 +0100 ---------------------------------------------------------------------- .../org/apache/deltaspike/data/api/Query.java | 6 + .../apache/deltaspike/data/api/QueryResult.java | 15 ++ .../apache/deltaspike/data/api/Repository.java | 2 +- .../deltaspike/data/api/SingleResultType.java | 46 ++++++ .../deltaspike/data/api/criteria/Criteria.java | 14 ++ .../data/impl/builder/part/QueryRoot.java | 21 ++- .../impl/builder/result/DefaultQueryResult.java | 21 +++ .../impl/builder/result/QueryProcessor.java | 4 +- .../builder/result/QueryProcessorFactory.java | 30 +++- .../data/impl/criteria/QueryCriteria.java | 21 +++ .../impl/handler/CdiQueryInvocationContext.java | 8 +- .../data/impl/handler/QueryHandler.java | 8 +- .../deltaspike/data/impl/meta/MethodPrefix.java | 145 +++++++++++++++++++ .../data/impl/meta/RepositoryComponent.java | 10 +- .../data/impl/meta/RepositoryMethod.java | 16 +- .../deltaspike/data/impl/QueryResultTest.java | 49 +++++++ .../data/impl/builder/part/QueryRootTest.java | 20 ++- .../data/impl/criteria/CriteriaTest.java | 50 +++++++ .../data/impl/handler/QueryHandlerTest.java | 95 ++++++++++++ .../test/service/SimpleCriteriaRepository.java | 14 ++ .../data/test/service/SimpleRepository.java | 17 ++- .../data/test/util/TestDeployments.java | 3 +- 22 files changed, 577 insertions(+), 38 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/Query.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/Query.java b/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/Query.java index 23970ce..e03410c 100755 --- a/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/Query.java +++ b/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/Query.java @@ -72,4 +72,10 @@ public @interface Query QueryHint[] hints() default { }; + /** + * Defines how a single result query is fetched. Defaults to the JPA way with + * Exceptions thrown on non-single result queries. + */ + SingleResultType singleResult() default SingleResultType.JPA; + } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/QueryResult.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/QueryResult.java b/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/QueryResult.java index f3289a9..fce1a87 100644 --- a/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/QueryResult.java +++ b/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/QueryResult.java @@ -149,6 +149,21 @@ public interface QueryResult<E> E getSingleResult(); /** + * Fetch a single result entity. Returns {@code null} if no result is found. + * + * @return Entity retrieved by the query, or {@code null} if no result. + */ + E getOptionalResult(); + + /** + * Fetch a single result entity. Returns {@code null} if no result is found. If the + * query finds multiple results, it simply returns the first one found. + * + * @return First Entity retrieved by the query, or {@code null} if no result. + */ + E getAnyResult(); + + /** * Count the result set. * @return Result count. */ http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/Repository.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/Repository.java b/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/Repository.java index 0f86131..f1a1308 100755 --- a/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/Repository.java +++ b/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/Repository.java @@ -47,6 +47,6 @@ public @interface Repository * The method prefix for method expressions. Can be adapted to * domain specific conventions. */ - String methodPrefix() default "findBy"; + String methodPrefix() default ""; } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/SingleResultType.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/SingleResultType.java b/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/SingleResultType.java new file mode 100644 index 0000000..c2a54e8 --- /dev/null +++ b/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/SingleResultType.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.apache.deltaspike.data.api; + +/** + * Defines the way a single result query is fetched. + */ +public enum SingleResultType +{ + + /** + * Expects a single result, throws a {@link javax.persistence.NoResultException} or + * {@link javax.persistence.NonUniqueResultException} otherwise. This is the JPA + * default behavior. + */ + JPA, + + /** + * Expects a single result. Other than {@link SingleResultStyle#SINGLE} it returns {@code null} + * if no result is found. + */ + OPTIONAL, + + /** + * Returns any result, or {@code null} if nothing is found. If more than one result is found, + * it returns the first one from the result list. + */ + ANY + +} http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/criteria/Criteria.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/criteria/Criteria.java b/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/criteria/Criteria.java index 909034b..6dfedd1 100644 --- a/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/criteria/Criteria.java +++ b/deltaspike/modules/data/api/src/main/java/org/apache/deltaspike/data/api/criteria/Criteria.java @@ -55,6 +55,20 @@ public interface Criteria<C, R> R getSingleResult(); /** + * Executes the query which has a single result. Returns {@code null} + * if there is no result. + * @return Entity matching the search query, or {@code null} if there is none. + */ + R getOptionalResult(); + + /** + * Executes the query and returns a single result. If there are + * multiple results, the first received is returned. + * @return Entity matching the search query. + */ + R getAnyResult(); + + /** * Creates a JPA query object to be executed. * @return A {@link TypedQuery} object ready to return results. */ http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/part/QueryRoot.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/part/QueryRoot.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/part/QueryRoot.java index 1774125..9484634 100644 --- a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/part/QueryRoot.java +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/part/QueryRoot.java @@ -28,6 +28,7 @@ import java.util.logging.Logger; import org.apache.deltaspike.data.impl.builder.MethodExpressionException; import org.apache.deltaspike.data.impl.builder.QueryBuilder; import org.apache.deltaspike.data.impl.builder.QueryBuilderContext; +import org.apache.deltaspike.data.impl.meta.MethodPrefix; import org.apache.deltaspike.data.impl.meta.RepositoryComponent; /** @@ -36,24 +37,24 @@ import org.apache.deltaspike.data.impl.meta.RepositoryComponent; public class QueryRoot extends QueryPart { - public static final QueryRoot UNKNOWN_ROOT = new QueryRoot("null-object", ""); + public static final QueryRoot UNKNOWN_ROOT = new QueryRoot("null-object", new MethodPrefix("", null)); private static final Logger log = Logger.getLogger(QueryRoot.class.getName()); private final String entityName; - private final String queryPrefix; + private final MethodPrefix methodPrefix; private String jpqlQuery; - protected QueryRoot(String entityName, String queryPrefix) + protected QueryRoot(String entityName, MethodPrefix methodPrefix) { this.entityName = entityName; - this.queryPrefix = queryPrefix; + this.methodPrefix = methodPrefix; } - public static QueryRoot create(String method, RepositoryComponent repo) + public static QueryRoot create(String method, RepositoryComponent repo, MethodPrefix prefix) { - QueryRoot root = new QueryRoot(repo.getEntityName(), repo.getMethodPrefix()); + QueryRoot root = new QueryRoot(repo.getEntityName(), prefix); root.build(method, method, repo); root.createJpql(); return root; @@ -121,16 +122,12 @@ public class QueryRoot extends QueryPart private boolean hasQueryConditions(String[] orderByParts) { - return !queryPrefix.equals(orderByParts[0]); + return !methodPrefix.getPrefix().equals(orderByParts[0]); } private String removePrefix(String queryPart) { - if (queryPart.startsWith(queryPrefix)) - { - return queryPart.substring(queryPrefix.length()); - } - return queryPart; + return methodPrefix.removePrefix(queryPart); } } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/DefaultQueryResult.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/DefaultQueryResult.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/DefaultQueryResult.java index a0fafb3..557d156 100644 --- a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/DefaultQueryResult.java +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/DefaultQueryResult.java @@ -23,6 +23,7 @@ import java.util.List; import javax.persistence.FlushModeType; import javax.persistence.LockModeType; +import javax.persistence.NoResultException; import javax.persistence.Query; import javax.persistence.metamodel.SingularAttribute; @@ -186,6 +187,26 @@ public class DefaultQueryResult<T> implements QueryResult<T> } @Override + public T getOptionalResult() + { + try + { + return getSingleResult(); + } + catch (NoResultException e) + { + return null; + } + } + + @Override + public T getAnyResult() + { + List<T> queryResult = getResultList(); + return queryResult.size() > 0 ? queryResult.get(0) : null; + } + + @Override public long count() { CountQueryPostProcessor counter = new CountQueryPostProcessor(); http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/QueryProcessor.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/QueryProcessor.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/QueryProcessor.java index b39b9ee..deb7993 100644 --- a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/QueryProcessor.java +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/QueryProcessor.java @@ -20,9 +20,11 @@ package org.apache.deltaspike.data.impl.builder.result; import javax.persistence.Query; +import org.apache.deltaspike.data.impl.handler.CdiQueryInvocationContext; + public interface QueryProcessor { - Object executeQuery(Query query); + Object executeQuery(Query query, CdiQueryInvocationContext context); } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/QueryProcessorFactory.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/QueryProcessorFactory.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/QueryProcessorFactory.java index 4b14616..022c22a 100644 --- a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/QueryProcessorFactory.java +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/builder/result/QueryProcessorFactory.java @@ -21,10 +21,13 @@ package org.apache.deltaspike.data.impl.builder.result; import java.lang.reflect.Method; import java.util.List; +import javax.persistence.NoResultException; import javax.persistence.Query; import org.apache.deltaspike.data.api.Modifying; import org.apache.deltaspike.data.api.QueryResult; +import org.apache.deltaspike.data.api.SingleResultType; +import org.apache.deltaspike.data.impl.handler.CdiQueryInvocationContext; public final class QueryProcessorFactory { @@ -74,7 +77,7 @@ public final class QueryProcessorFactory private static final class ListQueryProcessor implements QueryProcessor { @Override - public Object executeQuery(Query query) + public Object executeQuery(Query query, CdiQueryInvocationContext context) { return query.getResultList(); } @@ -83,7 +86,7 @@ public final class QueryProcessorFactory private static final class NoOpQueryProcessor implements QueryProcessor { @Override - public Object executeQuery(Query query) + public Object executeQuery(Query query, CdiQueryInvocationContext context) { return query; } @@ -92,9 +95,26 @@ public final class QueryProcessorFactory private static final class SingleResultQueryProcessor implements QueryProcessor { @Override - public Object executeQuery(Query query) + public Object executeQuery(Query query, CdiQueryInvocationContext context) { - return query.getSingleResult(); + SingleResultType style = context.getSingleResultStyle(); + switch (style) + { + case JPA: + return query.getSingleResult(); + case OPTIONAL: + try + { + return query.getSingleResult(); + } + catch (NoResultException e) + { + return null; + } + default: + List<Object> queryResult = query.getResultList(); + return queryResult.size() > 0 ? queryResult.get(0) : null; + } } } @@ -109,7 +129,7 @@ public final class QueryProcessorFactory } @Override - public Object executeQuery(Query query) + public Object executeQuery(Query query, CdiQueryInvocationContext context) { int result = query.executeUpdate(); if (!returnsVoid) http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/criteria/QueryCriteria.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/criteria/QueryCriteria.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/criteria/QueryCriteria.java index 63438db..0c054e3 100644 --- a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/criteria/QueryCriteria.java +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/criteria/QueryCriteria.java @@ -27,6 +27,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.persistence.EntityManager; +import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -112,6 +113,26 @@ public class QueryCriteria<C, R> implements Criteria<C, R> } @Override + public R getOptionalResult() + { + try + { + return getSingleResult(); + } + catch (NoResultException e) + { + return null; + } + } + + @Override + public R getAnyResult() + { + List<R> queryResult = getResultList(); + return queryResult.size() > 0 ? queryResult.get(0) : null; + } + + @Override public TypedQuery<R> createQuery() { try http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/CdiQueryInvocationContext.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/CdiQueryInvocationContext.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/CdiQueryInvocationContext.java index c4dd208..4549cb6 100644 --- a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/CdiQueryInvocationContext.java +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/CdiQueryInvocationContext.java @@ -25,6 +25,7 @@ import java.util.List; import javax.persistence.EntityManager; import javax.persistence.Query; +import org.apache.deltaspike.data.api.SingleResultType; import org.apache.deltaspike.data.api.mapping.QueryInOutMapper; import org.apache.deltaspike.data.impl.meta.RepositoryMethod; import org.apache.deltaspike.data.impl.param.Parameters; @@ -175,7 +176,7 @@ public class CdiQueryInvocationContext implements QueryInvocationContext public Object executeQuery(Query jpaQuery) { - return repoMethod.getQueryProcessor().executeQuery(jpaQuery); + return repoMethod.getQueryProcessor().executeQuery(jpaQuery, this); } public Parameters getParams() @@ -213,4 +214,9 @@ public class CdiQueryInvocationContext implements QueryInvocationContext return repoMethod.getQueryInOutMapperInstance(this); } + public SingleResultType getSingleResultStyle() + { + return repoMethod.getSingleResultStyle(); + } + } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/QueryHandler.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/QueryHandler.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/QueryHandler.java index 809a9a8..f743cb8 100755 --- a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/QueryHandler.java +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/handler/QueryHandler.java @@ -29,7 +29,9 @@ import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Inject; +import javax.persistence.PersistenceException; +import org.apache.deltaspike.data.api.QueryInvocationException; import org.apache.deltaspike.data.api.Repository; import org.apache.deltaspike.data.impl.builder.QueryBuilder; import org.apache.deltaspike.data.impl.builder.QueryBuilderFactory; @@ -76,9 +78,13 @@ public class QueryHandler implements Serializable, InvocationHandler Object result = builder.executeQuery(queryContext); return result; } + catch (PersistenceException e) + { + throw e; + } catch (Exception e) { - log.log(Level.SEVERE, "Query execution error", e); + log.log(Level.FINEST, "Query execution error", e); if (queryContext != null) { throw new QueryInvocationException(e, queryContext); http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/MethodPrefix.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/MethodPrefix.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/MethodPrefix.java new file mode 100644 index 0000000..81d8ea2 --- /dev/null +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/MethodPrefix.java @@ -0,0 +1,145 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * http://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.apache.deltaspike.data.impl.meta; + +import org.apache.deltaspike.data.api.SingleResultType; + +public class MethodPrefix +{ + public static final String DEFAULT_PREFIX = "findBy"; + public static final String DEFAULT_OPT_PREFIX = "findOptionalBy"; + public static final String DEFAULT_ANY_PREFIX = "findAnyBy"; + + private final String customPrefix; + private final String methodName; + + public MethodPrefix(String customPrefix, String methodName) + { + this.customPrefix = customPrefix; + this.methodName = methodName; + } + + public String removePrefix(String queryPart) + { + if (hasCustomPrefix() && queryPart.startsWith(customPrefix)) + { + return queryPart.substring(customPrefix.length()); + } + KnownQueryPrefix known = KnownQueryPrefix.fromMethodName(queryPart); + if (known != null) + { + return known.removePrefix(queryPart); + } + return queryPart; + } + + public boolean hasCustomPrefix() + { + return !"".equals(customPrefix); + } + + public String getCustomPrefix() + { + return customPrefix; + } + + public String getPrefix() + { + if (hasCustomPrefix()) + { + return customPrefix; + } + KnownQueryPrefix prefix = KnownQueryPrefix.fromMethodName(methodName); + if (prefix != null) + { + return prefix.getPrefix(); + } + return ""; + } + + public SingleResultType getSingleResultStyle() + { + KnownQueryPrefix prefix = KnownQueryPrefix.fromMethodName(methodName); + if (prefix != null) + { + return prefix.getStyle(); + } + return SingleResultType.JPA; + } + + private static enum KnownQueryPrefix + { + DEFAULT(DEFAULT_PREFIX) + { + @Override + public SingleResultType getStyle() + { + return SingleResultType.JPA; + } + }, + OPTIONAL(DEFAULT_OPT_PREFIX) + { + @Override + public SingleResultType getStyle() + { + return SingleResultType.OPTIONAL; + } + }, + ANY(DEFAULT_ANY_PREFIX) + { + @Override + public SingleResultType getStyle() + { + return SingleResultType.ANY; + } + }; + + private final String prefix; + + private KnownQueryPrefix(String prefix) + { + this.prefix = prefix; + } + + public String removePrefix(String queryPart) + { + return queryPart.substring(prefix.length()); + } + + public String getPrefix() + { + return prefix; + } + + public abstract SingleResultType getStyle(); + + public static KnownQueryPrefix fromMethodName(String name) + { + for (KnownQueryPrefix mapping : values()) + { + if (name.startsWith(mapping.getPrefix())) + { + return mapping; + } + } + return null; + } + } + +} http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/RepositoryComponent.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/RepositoryComponent.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/RepositoryComponent.java index 2943555..25f9cc4 100644 --- a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/RepositoryComponent.java +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/RepositoryComponent.java @@ -169,11 +169,6 @@ public class RepositoryComponent return repoClass; } - public String getMethodPrefix() - { - return repoClass.getAnnotation(Repository.class).methodPrefix(); - } - public boolean hasEntityManagerResolver() { return getEntityManagerResolverClass() != null; @@ -255,4 +250,9 @@ public class RepositoryComponent return null; } + public String getCustomMethodPrefix() + { + return repoClass.getAnnotation(Repository.class).methodPrefix(); + } + } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/RepositoryMethod.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/RepositoryMethod.java b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/RepositoryMethod.java index ec79d3f..ac90562 100644 --- a/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/RepositoryMethod.java +++ b/deltaspike/modules/data/impl/src/main/java/org/apache/deltaspike/data/impl/meta/RepositoryMethod.java @@ -32,6 +32,7 @@ import org.apache.deltaspike.core.api.provider.BeanManagerProvider; import org.apache.deltaspike.core.api.provider.BeanProvider; import org.apache.deltaspike.core.api.provider.DependentProvider; import org.apache.deltaspike.data.api.Query; +import org.apache.deltaspike.data.api.SingleResultType; import org.apache.deltaspike.data.api.mapping.MappingConfig; import org.apache.deltaspike.data.api.mapping.QueryInOutMapper; import org.apache.deltaspike.data.impl.builder.MethodExpressionException; @@ -55,6 +56,7 @@ public class RepositoryMethod private final Method method; private final MethodType methodType; + private final MethodPrefix methodPrefix; private final RepositoryComponent repo; private final QueryRoot queryRoot; private final QueryProcessor queryProcessor; @@ -66,6 +68,7 @@ public class RepositoryMethod { this.method = method; this.repo = repo; + this.methodPrefix = new MethodPrefix(repo.getCustomMethodPrefix(), method.getName()); this.methodType = extractMethodType(); this.queryRoot = initQueryRoot(); this.queryProcessor = QueryProcessorFactory.newInstance(method).build(); @@ -115,7 +118,7 @@ public class RepositoryMethod { if (methodType == MethodType.PARSE) { - return QueryRoot.create(method.getName(), repo); + return QueryRoot.create(method.getName(), repo, methodPrefix); } return QueryRoot.UNKNOWN_ROOT; } @@ -143,7 +146,7 @@ public class RepositoryMethod } try { - QueryRoot.create(method.getName(), repo); + QueryRoot.create(method.getName(), repo, methodPrefix); return true; } catch (MethodExpressionException e) @@ -218,4 +221,13 @@ public class RepositoryMethod return mapper != null; } + public SingleResultType getSingleResultStyle() + { + if (method.isAnnotationPresent(Query.class)) + { + return method.getAnnotation(Query.class).singleResult(); + } + return methodPrefix.getSingleResultStyle(); + } + } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/QueryResultTest.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/QueryResultTest.java b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/QueryResultTest.java index 90bcc9a..55db66f 100644 --- a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/QueryResultTest.java +++ b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/QueryResultTest.java @@ -21,6 +21,7 @@ package org.apache.deltaspike.data.impl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -31,6 +32,7 @@ import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.FlushModeType; import javax.persistence.LockModeType; +import javax.persistence.NonUniqueResultException; import javax.persistence.PersistenceContext; import org.apache.deltaspike.data.api.QueryResult; @@ -280,6 +282,53 @@ public class QueryResultTest extends TransactionalTestCase assertEquals(2L, result); } + @Test + public void should_query_optional() + { + // given + final String name = "should_query_optional"; + builder.createSimple(name); + + // when + Simple result1 = repo.queryResultWithNamed(name).getOptionalResult(); + Simple result2 = repo.queryResultWithNamed("this_does_not_exist").getOptionalResult(); + + // then + assertNotNull(result1); + assertEquals(name, result1.getName()); + assertNull(result2); + } + + @Test(expected = NonUniqueResultException.class) + public void should_fail_query_optional_with_nonunique() + { + // given + final String name = "should_fail_query_optional_with_nonunique"; + builder.createSimple(name); + builder.createSimple(name); + + // when + repo.queryResultWithNamed(name).getOptionalResult(); + } + + @Test + public void should_query_any() + { + // given + final String name = "should_query_any"; + builder.createSimple(name); + builder.createSimple(name); + + // when + Simple result1 = repo.queryResultWithNamed(name).getAnyResult(); + Simple result2 = repo.queryResultWithNamed("this_does_not_exist").getAnyResult(); + + // then + assertNotNull(result1); + assertEquals(name, result1.getName()); + assertNull(result2); + } + @Before public void setup() { http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/builder/part/QueryRootTest.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/builder/part/QueryRootTest.java b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/builder/part/QueryRootTest.java index 4279e62..3184669 100644 --- a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/builder/part/QueryRootTest.java +++ b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/builder/part/QueryRootTest.java @@ -21,6 +21,7 @@ package org.apache.deltaspike.data.impl.builder.part; import static org.junit.Assert.assertEquals; import org.apache.deltaspike.data.impl.builder.MethodExpressionException; +import org.apache.deltaspike.data.impl.meta.MethodPrefix; import org.apache.deltaspike.data.impl.meta.RepositoryComponent; import org.apache.deltaspike.data.impl.meta.RepositoryEntity; import org.apache.deltaspike.data.test.domain.Simple; @@ -43,7 +44,7 @@ public class QueryRootTest "where e.name = ?1"; // when - String result = QueryRoot.create(name, repo).getJpqlQuery().trim(); + String result = QueryRoot.create(name, repo, prefix(name)).getJpqlQuery().trim(); // then assertEquals(expected, result); @@ -65,7 +66,7 @@ public class QueryRootTest "order by e.embedded.embedd desc"; // when - String result = QueryRoot.create(name, repo).getJpqlQuery().trim(); + String result = QueryRoot.create(name, repo, prefix(name)).getJpqlQuery().trim(); // then assertEquals(expected, result); @@ -81,7 +82,7 @@ public class QueryRootTest "order by e.id asc"; // when - String result = QueryRoot.create(name, repo).getJpqlQuery().trim(); + String result = QueryRoot.create(name, repo, prefix(name)).getJpqlQuery().trim(); // then assertEquals(expected, result); @@ -94,7 +95,7 @@ public class QueryRootTest final String name = "findByInvalid"; // when - QueryRoot.create(name, repo); + QueryRoot.create(name, repo, prefix(name)); } @Test(expected = MethodExpressionException.class) @@ -104,7 +105,7 @@ public class QueryRootTest final String name = "findBy"; // when - QueryRoot.create(name, repo); + QueryRoot.create(name, repo, prefix(name)); } @Test(expected = MethodExpressionException.class) @@ -114,7 +115,7 @@ public class QueryRootTest final String name = "findByNameOrderByInvalidDesc"; // when - QueryRoot.create(name, repo); + QueryRoot.create(name, repo, prefix(name)); } @Test @@ -127,10 +128,15 @@ public class QueryRootTest "where e.name = ?1"; // when - String result = QueryRoot.create(name, repoFetchBy).getJpqlQuery().trim(); + String result = QueryRoot.create(name, repoFetchBy, new MethodPrefix("fetchBy", name)).getJpqlQuery().trim(); // then assertEquals(expected, result); } + private MethodPrefix prefix(final String name) + { + return new MethodPrefix("", name); + } + } http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/criteria/CriteriaTest.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/criteria/CriteriaTest.java b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/criteria/CriteriaTest.java index 8368c7b..06a4d2d 100644 --- a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/criteria/CriteriaTest.java +++ b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/criteria/CriteriaTest.java @@ -20,8 +20,10 @@ package org.apache.deltaspike.data.impl.criteria; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.lang.reflect.InvocationTargetException; import java.util.List; import javax.enterprise.inject.Produces; @@ -289,6 +291,54 @@ public class CriteriaTest extends TransactionalTestCase } } + @Test + public void should_create_select_criteria_with_optional_result() + { + // given + final String name = "should_create_select_criteria_with_optional_result"; + createSimple(name, 10); + + // when + Simple result1 = repo.queryOptional(name); + Simple result2 = repo.queryOptional(name + "_doesnt exist"); + + // then + assertNotNull(result1); + assertEquals(name, result1.getName()); + assertNull(result2); + } + + @Test(expected = InvocationTargetException.class) + public void should_fail_with_optional_nonunique_result() + { + // given + final String name = "should_fail_with_optional_nonunique_result"; + createSimple(name, 10); + createSimple(name, 10); + + // when + repo.queryOptional(name); + + } + + @Test + public void should_create_select_criteria_with_any_result() + { + // given + final String name = "should_create_select_criteria_with_any_result"; + createSimple(name, 10); + createSimple(name, 10); + + // when + Simple result1 = repo.queryAny(name); + Simple result2 = repo.queryAny(name + "_doesnt exist"); + + // then + assertNotNull(result1); + assertEquals(name, result1.getName()); + assertNull(result2); + } + @Override protected EntityManager getEntityManager() { http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/handler/QueryHandlerTest.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/handler/QueryHandlerTest.java b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/handler/QueryHandlerTest.java index b1076d0..712eeb5 100644 --- a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/handler/QueryHandlerTest.java +++ b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/impl/handler/QueryHandlerTest.java @@ -21,6 +21,7 @@ package org.apache.deltaspike.data.impl.handler; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.List; @@ -274,6 +275,100 @@ public class QueryHandlerTest extends TransactionalTestCase assertEquals(1, count); } + @Test + public void should_create_optinal_query_by_name() + { + // given + final String name = "should_create_optinal_query_by_name"; + builder.createSimple(name); + + // when + Simple result1 = repo.findOptionalByName(name); + Simple result2 = repo.findOptionalByName(name + "_doesnt_exist"); + + // then + assertNotNull(result1); + assertEquals(name, result1.getName()); + assertNull(result2); + } + + @Test + public void should_create_optinal_query_by_annotation() + { + // given + final String name = "should_create_optinal_query_by_annotation"; + builder.createSimple(name); + + // when + Simple result1 = repo.findByNameOptional(name); + Simple result2 = repo.findByNameOptional(name + "_doesnt_exist"); + + // then + assertNotNull(result1); + assertEquals(name, result1.getName()); + assertNull(result2); + } + + @Test(expected = QueryInvocationException.class) + public void should_fail_optinal_query_by_name_with_nonunique() + { + // given + final String name = "should_fail_optinal_query_by_name_with_nonunique"; + builder.createSimple(name); + builder.createSimple(name); + + // when + repo.findOptionalByName(name); + } + + @Test(expected = QueryInvocationException.class) + public void should_fail_optinal_query_by_annotation_with_nonunique() + { + // given + final String name = "should_fail_optinal_query_by_annotation_with_nonunique"; + builder.createSimple(name); + builder.createSimple(name); + + // when + repo.findByNameOptional(name); + } + + @Test + public void should_create_any_query_by_name() + { + // given + final String name = "should_create_any_query_by_name"; + builder.createSimple(name); + builder.createSimple(name); + + // when + Simple result1 = repo.findAnyByName(name); + Simple result2 = repo.findAnyByName(name + "_doesnt_exist"); + + // then + assertNotNull(result1); + assertEquals(name, result1.getName()); + assertNull(result2); + } + + @Test + public void should_create_any_query_by_annotation() + { + // given + final String name = "should_create_any_query_by_annotation"; + builder.createSimple(name); + builder.createSimple(name); + + // when + Simple result1 = repo.findByNameAny(name); + Simple result2 = repo.findByNameAny(name + "_doesnt_exist"); + + // then + assertNotNull(result1); + assertEquals(name, result1.getName()); + assertNull(result2); + } + @Before public void setup() { http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/service/SimpleCriteriaRepository.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/service/SimpleCriteriaRepository.java b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/service/SimpleCriteriaRepository.java index 3c5a159..a4cca1c 100644 --- a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/service/SimpleCriteriaRepository.java +++ b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/service/SimpleCriteriaRepository.java @@ -40,6 +40,20 @@ public abstract class SimpleCriteriaRepository extends AbstractEntityRepository< .getResultList(); } + public Simple queryOptional(String name) + { + return criteria() + .eq(Simple_.name, name) + .getOptionalResult(); + } + + public Simple queryAny(String name) + { + return criteria() + .eq(Simple_.name, name) + .getAnyResult(); + } + @SuppressWarnings("unchecked") public Statistics queryWithSelect(String name) { http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/service/SimpleRepository.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/service/SimpleRepository.java b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/service/SimpleRepository.java index 21c41cb..f3d1d07 100755 --- a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/service/SimpleRepository.java +++ b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/service/SimpleRepository.java @@ -18,10 +18,13 @@ */ package org.apache.deltaspike.data.test.service; +import static javax.persistence.LockModeType.PESSIMISTIC_WRITE; +import static org.apache.deltaspike.data.api.SingleResultType.ANY; +import static org.apache.deltaspike.data.api.SingleResultType.OPTIONAL; + import java.util.List; import javax.persistence.EntityManager; -import javax.persistence.LockModeType; import org.apache.deltaspike.data.api.AbstractEntityRepository; import org.apache.deltaspike.data.api.FirstResult; @@ -48,11 +51,17 @@ public abstract class SimpleRepository extends AbstractEntityRepository<Simple, @Query(named = Simple.BY_NAME_ENABLED, max = 1) public abstract List<Simple> findByNamedQueryIndexed(String name, Boolean enabled); + @Query(named = Simple.BY_NAME_LIKE, singleResult = OPTIONAL) + public abstract Simple findByNameOptional(String name); + + @Query(named = Simple.BY_NAME_LIKE, singleResult = ANY) + public abstract Simple findByNameAny(String name); + @Query(named = Simple.BY_NAME_ENABLED) public abstract List<Simple> findByNamedQueryRestricted(String name, Boolean enabled, @MaxResults int max, @FirstResult Integer first); - @Query(named = Simple.BY_ID, lock = LockModeType.PESSIMISTIC_WRITE) + @Query(named = Simple.BY_ID, lock = PESSIMISTIC_WRITE) public abstract Simple findByNamedQueryNamed( @QueryParam("id") Long id, @QueryParam("enabled") Boolean enabled); @@ -64,6 +73,10 @@ public abstract class SimpleRepository extends AbstractEntityRepository<Simple, public abstract Simple findByNameAndEnabled(String name, Boolean enabled); + public abstract Simple findOptionalByName(String name); + + public abstract Simple findAnyByName(String name); + public abstract List<Simple> findByOrderByCounterAscIdDesc(); @Query(value = "SELECT * from SIMPLE_TABLE s WHERE s.name = ?1", isNative = true) http://git-wip-us.apache.org/repos/asf/deltaspike/blob/f3c00d8c/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/util/TestDeployments.java ---------------------------------------------------------------------- diff --git a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/util/TestDeployments.java b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/util/TestDeployments.java index c93ce26..f5ada14 100755 --- a/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/util/TestDeployments.java +++ b/deltaspike/modules/data/impl/src/test/java/org/apache/deltaspike/data/test/util/TestDeployments.java @@ -29,6 +29,7 @@ import org.apache.deltaspike.data.api.Query; import org.apache.deltaspike.data.api.QueryParam; import org.apache.deltaspike.data.api.QueryResult; import org.apache.deltaspike.data.api.Repository; +import org.apache.deltaspike.data.api.SingleResultType; import org.apache.deltaspike.data.api.audit.CreatedOn; import org.apache.deltaspike.data.api.audit.CurrentUser; import org.apache.deltaspike.data.api.audit.ModifiedBy; @@ -121,7 +122,7 @@ public abstract class TestDeployments .addClasses(AbstractEntityRepository.class, Repository.class, EntityRepository.class, FirstResult.class, MaxResults.class, Modifying.class, Query.class, QueryParam.class, QueryResult.class, - EntityManagerConfig.class, EntityManagerResolver.class) + EntityManagerConfig.class, EntityManagerResolver.class, SingleResultType.class) .addClasses(Criteria.class, QuerySelection.class, CriteriaSupport.class) .addClasses(CreatedOn.class, CurrentUser.class, ModifiedBy.class, ModifiedOn.class) .addClasses(MappingConfig.class, QueryInOutMapper.class)
