diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..06a9297 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: java +jdk: + - oraclejdk8 +before_install: + - curl -o $HOME/.m2/settings.xml https://raw.githubusercontent.com/Cosium/spring-data-jpa-entity-graph/$TRAVIS_BRANCH/src/site/resources/settings.xml +after_success: + - bash <(curl -s https://codecov.io/bash) \ No newline at end of file diff --git a/README.md b/README.md index 0978adb..8afe82e 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,37 @@ -### Why? +[![Travis branch](https://img.shields.io/travis/Cosium/spring-data-jpa-entity-graph/1.10.x.svg)](https://travis-ci.org/Cosium/spring-data-jpa-entity-graph) +[![Codecov branch](https://img.shields.io/codecov/c/github/Cosium/spring-data-jpa-entity-graph/1.10.x.svg)](https://codecov.io/gh/Cosium/spring-data-jpa-entity-graph) +[![Maven Central](https://img.shields.io/maven-central/v/com.cosium.spring.data/spring-data-jpa-entity-graph.svg)](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.cosium.spring.data%22%20AND%20a%3A%22spring-data-jpa-entity-graph%22) -Today, Spring Data JPA supports EntityGraph exlusively through annotations. +# Spring Data JPA EntityGraph + +Today, [Spring Data JPA](https://github.com/spring-projects/spring-data-jpa) supports EntityGraph exlusively through annotations. Thus, for a method, the choice of EntityGraph must be made before compilation. -This library gives the ability to pass EntityGraph on any Spring Data JPA repository method as an argument, making the EntityGraph choice fully dynamic. +This extension gives the ability to pass EntityGraph on any Spring Data JPA repository method as an argument, making the EntityGraph choice fully dynamic. + +Example: +```java +productRepository.findByName("foo", EntityGraphUtils.fromName("Product.brand")); +``` + +## Compatibility + +This library follows the Spring Data JPA versionning semantic. + +spring-data-jpa-entity-graph | spring-data-jpa +---------------------------- | --------------- +1.11.x | 1.11.y +1.10.x | 1.10.y -### Quick start in 3 steps +## Quick start -1. Add the dependency : +1. In addition to spring-data-jpa, add the library dependency : ```xml com.cosium.spring.data spring-data-jpa-entity-graph - 1.11.00-SNAPSHOT + 1.10.13 ``` 2. In your Spring configuration, set the repository factory bean class to `JpaEntityGraphRepositoryFactoryBean` : @@ -27,7 +45,7 @@ This library gives the ability to pass EntityGraph on any Spring Data JPA reposi ``` 3. Make sure your repositories extend `JpaEntityGraphRepository`, `JpaEntityGraphSpecificationExecutor` and/or `JpaEntityGraphQueryDslPredicateExecutor` -### Usage +## Basic Usage Let's consider the following entities and repository : ```java @@ -63,14 +81,44 @@ public interface ProductRepository extends JpaEntityGraphRepository - + 4.0.0 + Spring Data JPA EntityGraph + A Spring Data JPA extension allowing full usage of JPA EntityGraph on repositories + https://github.com/Cosium/spring-data-jpa-entity-graph + com.cosium.spring.data spring-data-jpa-entity-graph - 1.11.00-SNAPSHOT + 1.10.14-SNAPSHOT 1.6 - 1.11.0.BUILD-SNAPSHOT + 1.10.5.RELEASE 2.6.2 4.1.3 2.5.3 1.3.0 - 1.4.1.RELEASE + 1.3.8.RELEASE

1.4.193

@@ -86,6 +88,12 @@ ${dbunit} test + + org.assertj + assertj-core + 3.3.0 + test + @@ -146,7 +154,7 @@ false true true - false + true release deploy @@ -168,15 +176,20 @@ - org.apache.maven.plugins - maven-gpg-plugin - 1.6 + org.jacoco + jacoco-maven-plugin + 0.7.7.201606060606 - sign-artifacts - deploy - sign + prepare-agent + + + + report + test + + report @@ -184,6 +197,54 @@ + + + release-sign-artifacts + + + performRelease + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + scm:git:https://github.com/Cosium/spring-data-jpa-entity-graph + scm:git:https://github.com/Cosium/spring-data-jpa-entity-graph + https://github.com/Cosium/spring-data-jpa-entity-graph + HEAD + + Cosium http://www.cosium.com @@ -198,15 +259,12 @@ - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - + + + MIT License + http://www.opensource.org/licenses/mit-license.php + repo + +
\ No newline at end of file diff --git a/release.sh b/release.sh new file mode 100755 index 0000000..03c6696 --- /dev/null +++ b/release.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +mvn --batch-mode clean release:prepare release:perform && git push && git push --tags \ No newline at end of file diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/AbstractEntityGraph.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/AbstractEntityGraph.java index 5470609..80c07a2 100644 --- a/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/AbstractEntityGraph.java +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/AbstractEntityGraph.java @@ -1,5 +1,6 @@ package com.cosium.spring.data.jpa.entity.graph.domain; +import com.google.common.base.MoreObjects; import org.springframework.util.Assert; /** @@ -9,22 +10,52 @@ */ public abstract class AbstractEntityGraph implements EntityGraph{ - private EntityGraphType type = EntityGraphType.FETCH; + protected static final EntityGraphType DEFAULT_ENTITY_GRAPH_TYPE = EntityGraphType.FETCH; + private EntityGraphType entityGraphType = DEFAULT_ENTITY_GRAPH_TYPE; + private boolean optional; public AbstractEntityGraph(){} - public AbstractEntityGraph(EntityGraphType type){ - Assert.notNull(type); - this.type = type; + public AbstractEntityGraph(EntityGraphType entityGraphType){ + this(entityGraphType, false); + } + + public AbstractEntityGraph(EntityGraphType entityGraphType, boolean optional){ + Assert.notNull(entityGraphType); + this.entityGraphType = entityGraphType; + this.optional = optional; } @Override - public EntityGraphType getType() { - return type; + public EntityGraphType getEntityGraphType() { + return entityGraphType; + } + + public void setEntityGraphType(EntityGraphType entityGraphType) { + Assert.notNull(entityGraphType); + this.entityGraphType = entityGraphType; } - public void setType(EntityGraphType type) { - Assert.notNull(type); - this.type = type; + /** + * False by default + * @return True if the EntityGraph is optional.
+ * Passing an optional EntityGraph to an unsupported method will not trigger {@link com.cosium.spring.data.jpa.entity.graph.repository.exception.InapplicableEntityGraphException}. + */ + @Override + public boolean isOptional() { + return optional; } + + public void setOptional(boolean optional) { + this.optional = optional; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("entityGraphType", entityGraphType) + .add("optional", optional) + .toString(); + } + } diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/DynamicEntityGraph.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/DynamicEntityGraph.java index 999c96b..737f49c 100644 --- a/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/DynamicEntityGraph.java +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/DynamicEntityGraph.java @@ -3,6 +3,8 @@ import java.util.Collections; import java.util.List; +import com.google.common.base.MoreObjects; + /** * Created on 22/11/16. * @@ -22,12 +24,20 @@ public DynamicEntityGraph(EntityGraphType type, List attributePaths){ } @Override - public List getAttributePaths() { + public List getEntityGraphAttributePaths() { return attributePaths; } @Override - public final String getName() { + public final String getEntityGraphName() { return null; } + + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("attributePaths", attributePaths) + .toString(); + } } diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/EntityGraph.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/EntityGraph.java index c2c7ae8..3931c3a 100644 --- a/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/EntityGraph.java +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/EntityGraph.java @@ -10,17 +10,23 @@ public interface EntityGraph { /** - * @return The type of the entity graph. Can't be null. + * @return The type of the entity graph. May be null. */ - EntityGraphType getType(); + EntityGraphType getEntityGraphType(); /** - * @return The name to use to retrieve the EntityGraph. May be null or empty + * @return The name to use to retrieve the EntityGraph. May be null. */ - String getName(); + String getEntityGraphName(); /** * @return The attribute paths. May be null. */ - List getAttributePaths(); + List getEntityGraphAttributePaths(); + + /** + * @return True if the EntityGraph is optional.
+ * Passing an optional EntityGraph to an unsupported method will not trigger {@link com.cosium.spring.data.jpa.entity.graph.repository.exception.InapplicableEntityGraphException}. + */ + boolean isOptional(); } diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/EntityGraphUtils.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/EntityGraphUtils.java index 660a956..c49bbba 100644 --- a/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/EntityGraphUtils.java +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/EntityGraphUtils.java @@ -1,5 +1,7 @@ package com.cosium.spring.data.jpa.entity.graph.domain; +import java.util.List; + /** * Created on 22/11/16. * @@ -7,8 +9,55 @@ */ public class EntityGraphUtils { - public static EntityGraph fromName(String name){ + private static final EntityGraph EMPTY_ENTITY_GRAPH = new EmptyEntityGraph(); + + /** + * @return An empty EntityGraph + */ + public static EntityGraph empty() { + return EMPTY_ENTITY_GRAPH; + } + + /** + * @param name The name of the targeted EntityGraph + * @return An EntityGraph referenced by name + */ + public static EntityGraph fromName(String name) { return new NamedEntityGraph(name); } + /** + * + * @param name The name of the targeted EntityGraph + * @param optional Is the EntityGraph usage optional? + * @return An EntityGraph referenced by name + */ + public static EntityGraph fromName(String name, boolean optional){ + NamedEntityGraph namedEntityGraph = new NamedEntityGraph(name); + namedEntityGraph.setOptional(optional); + return namedEntityGraph; + } + + private static final class EmptyEntityGraph implements EntityGraph { + + @Override + public EntityGraphType getEntityGraphType() { + return null; + } + + @Override + public String getEntityGraphName() { + return null; + } + + @Override + public List getEntityGraphAttributePaths() { + return null; + } + + @Override + public boolean isOptional() { + return false; + } + } } diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/NamedEntityGraph.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/NamedEntityGraph.java index 7e8f790..7b42357 100644 --- a/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/NamedEntityGraph.java +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/domain/NamedEntityGraph.java @@ -2,6 +2,7 @@ import java.util.List; +import com.google.common.base.MoreObjects; import org.springframework.util.Assert; /** @@ -13,25 +14,34 @@ public class NamedEntityGraph extends AbstractEntityGraph { private final String name; - public NamedEntityGraph(EntityGraphType type, String name) { - super(type); + public NamedEntityGraph(EntityGraphType type, boolean optional, String name) { + super(type, optional); Assert.hasLength(name); this.name = name; } + public NamedEntityGraph(EntityGraphType type, String name) { + this(type, false, name); + } + public NamedEntityGraph(String name){ - Assert.hasLength(name); - this.name = name; + this(DEFAULT_ENTITY_GRAPH_TYPE, name); } @Override - public String getName() { + public String getEntityGraphName() { return name; } @Override - public final List getAttributePaths() { + public final List getEntityGraphAttributePaths() { return null; } + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", name) + .toString(); + } } diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/InapplicableEntityGraphException.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/InapplicableEntityGraphException.java new file mode 100644 index 0000000..315601f --- /dev/null +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/InapplicableEntityGraphException.java @@ -0,0 +1,13 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.exception; + +/** + * Created on 27/11/16. + * + * @author Reda.Housni-Alaoui + */ +public class InapplicableEntityGraphException extends RepositoryEntityGraphException { + + public InapplicableEntityGraphException(String message) { + super(message); + } +} diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/MultipleDefaultEntityGraphException.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/MultipleDefaultEntityGraphException.java new file mode 100644 index 0000000..b426f3c --- /dev/null +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/MultipleDefaultEntityGraphException.java @@ -0,0 +1,14 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.exception; + + +/** + * Created on 27/11/16. + * + * @author Reda.Housni-Alaoui + */ +public class MultipleDefaultEntityGraphException extends RepositoryEntityGraphException { + + public MultipleDefaultEntityGraphException(String entityGraph1Name, String entityGraph2Name) { + super("Multiple default entity graphs detected : " + entityGraph1Name + " and " + entityGraph2Name); + } +} diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/MultipleEntityGraphException.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/MultipleEntityGraphException.java new file mode 100644 index 0000000..61d3bb6 --- /dev/null +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/MultipleEntityGraphException.java @@ -0,0 +1,13 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.exception; + +/** + * Created on 27/11/16. + * + * @author Reda.Housni-Alaoui + */ +public class MultipleEntityGraphException extends RuntimeException { + + public MultipleEntityGraphException(String message) { + super(message); + } +} diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/RepositoryEntityGraphException.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/RepositoryEntityGraphException.java new file mode 100644 index 0000000..39cea7a --- /dev/null +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/exception/RepositoryEntityGraphException.java @@ -0,0 +1,14 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.exception; + +/** + * Created on 27/11/16. + * + * @author Reda.Housni-Alaoui + */ +public abstract class RepositoryEntityGraphException extends RuntimeException { + + public RepositoryEntityGraphException(String message) { + super(message); + } + +} diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/CountQueryDetector.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/CountQueryDetector.java new file mode 100644 index 0000000..187fbf6 --- /dev/null +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/CountQueryDetector.java @@ -0,0 +1,48 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.support; + + +import com.querydsl.jpa.JPQLQuery; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.core.NamedThreadLocal; + +/** + * Created on 05/12/16. + * + * @author Reda.Housni-Alaoui + */ +class CountQueryDetector implements MethodInterceptor { + + private static final CountQueryDetector INSTANCE = new CountQueryDetector(); + private static final String FETCH_METHOD_NAME_PREFIX = "fetch"; + + private static final NamedThreadLocal IS_COUNT_QUERY = new NamedThreadLocal("A thread local holding a boolean describing " + + "the fact that the current query is count query"); + + private CountQueryDetector(){ + + } + + static JPQLQuery proxy(JPQLQuery countQuery){ + ProxyFactory proxyFactory = new ProxyFactory(countQuery); + proxyFactory.addAdvice(INSTANCE); + return (JPQLQuery) proxyFactory.getProxy(); + } + + static boolean isCountQuery(){ + return IS_COUNT_QUERY.get() == null? false: IS_COUNT_QUERY.get(); + } + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + if(invocation.getMethod().getName().startsWith(FETCH_METHOD_NAME_PREFIX)){ + IS_COUNT_QUERY.set(true); + } + try{ + return invocation.proceed(); + } finally { + IS_COUNT_QUERY.set(false); + } + } +} diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/EntityGraphAwareQueryDslJpaRepository.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/EntityGraphAwareQueryDslJpaRepository.java new file mode 100644 index 0000000..9653396 --- /dev/null +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/EntityGraphAwareQueryDslJpaRepository.java @@ -0,0 +1,31 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.support; + +import javax.persistence.EntityManager; +import java.io.Serializable; + +import com.querydsl.core.types.Predicate; +import com.querydsl.jpa.JPQLQuery; +import org.springframework.data.jpa.repository.support.JpaEntityInformation; +import org.springframework.data.jpa.repository.support.QueryDslJpaRepository; +import org.springframework.data.querydsl.EntityPathResolver; + +/** + * Created on 05/12/16. + * + * @author Reda.Housni-Alaoui + */ +class EntityGraphAwareQueryDslJpaRepository extends QueryDslJpaRepository { + + public EntityGraphAwareQueryDslJpaRepository(JpaEntityInformation entityInformation, EntityManager entityManager) { + super(entityInformation, entityManager); + } + + public EntityGraphAwareQueryDslJpaRepository(JpaEntityInformation entityInformation, EntityManager entityManager, EntityPathResolver resolver) { + super(entityInformation, entityManager, resolver); + } + + @Override + protected JPQLQuery createCountQuery(Predicate predicate) { + return CountQueryDetector.proxy(super.createCountQuery(predicate)); + } +} diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/EntityGraphBean.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/EntityGraphBean.java index 4a5cbb0..bc1f0cf 100644 --- a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/EntityGraphBean.java +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/EntityGraphBean.java @@ -1,25 +1,49 @@ package com.cosium.spring.data.jpa.entity.graph.repository.support; +import com.google.common.base.MoreObjects; +import org.springframework.core.ResolvableType; import org.springframework.data.jpa.repository.query.JpaEntityGraph; import org.springframework.util.Assert; /** * Wrapper class allowing to hold a {@link JpaEntityGraph} with its associated domain class. - * * Created on 23/11/16. * * @author Reda.Housni-Alaoui */ class EntityGraphBean { + private final JpaEntityGraph jpaEntityGraph; private final Class domainClass; + private final ResolvableType repositoryMethodReturnType; + private final boolean optional; + private final boolean primary; + private final boolean valid; - public EntityGraphBean(JpaEntityGraph jpaEntityGraph, Class domainClass) { + public EntityGraphBean(JpaEntityGraph jpaEntityGraph, Class domainClass, ResolvableType repositoryMethodReturnType, boolean optional, boolean primary) { Assert.notNull(jpaEntityGraph); Assert.notNull(domainClass); + Assert.notNull(repositoryMethodReturnType); this.jpaEntityGraph = jpaEntityGraph; this.domainClass = domainClass; + this.repositoryMethodReturnType = repositoryMethodReturnType; + this.optional = optional; + this.primary = primary; + this.valid = computeValidity(); + } + + private boolean computeValidity() { + Class resolvedReturnType = repositoryMethodReturnType.resolve(); + if (Void.TYPE.equals(resolvedReturnType) || domainClass.isAssignableFrom(resolvedReturnType)) { + return true; + } + for (Class genericType : repositoryMethodReturnType.resolveGenerics()) { + if (domainClass.isAssignableFrom(genericType)) { + return true; + } + } + return false; } /** @@ -35,4 +59,35 @@ public JpaEntityGraph getJpaEntityGraph() { public Class getDomainClass() { return domainClass; } + + /** + * @return True if this entity graph is not mandatory + */ + public boolean isOptional() { + return optional; + } + + /** + * @return True if this EntityGraph seems valid + */ + public boolean isValid() { + return valid; + } + + /** + * @return True if this EntityGraph is a primary one. Default EntityGraph is an example of non primary EntityGraph. + */ + public boolean isPrimary() { + return primary; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("jpaEntityGraph", jpaEntityGraph) + .add("domainClass", domainClass) + .add("repositoryMethodReturnType", repositoryMethodReturnType) + .add("optional", optional) + .toString(); + } } diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/JpaEntityGraphRepositoryFactory.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/JpaEntityGraphRepositoryFactory.java index f835915..b93f684 100644 --- a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/JpaEntityGraphRepositoryFactory.java +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/JpaEntityGraphRepositoryFactory.java @@ -9,7 +9,6 @@ import java.util.List; import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraph; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.support.JpaRepositoryFactory; import org.springframework.data.querydsl.QueryDslPredicateExecutor; import org.springframework.data.repository.core.RepositoryMetadata; @@ -62,7 +61,7 @@ private static void addEntityGraphToSpecialTypes(Class clazz, String fieldNam */ public JpaEntityGraphRepositoryFactory(EntityManager entityManager) { super(entityManager); - addRepositoryProxyPostProcessor(new RepositoryMethodEntityGraphExtractor()); + addRepositoryProxyPostProcessor(new RepositoryMethodEntityGraphExtractor(entityManager)); } @Override diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/QueryDslJpaEntityGraphRepository.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/QueryDslJpaEntityGraphRepository.java index 6105f1a..ff98722 100644 --- a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/QueryDslJpaEntityGraphRepository.java +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/QueryDslJpaEntityGraphRepository.java @@ -28,13 +28,13 @@ public class QueryDslJpaEntityGraphRepository public QueryDslJpaEntityGraphRepository(JpaEntityInformation entityInformation, EntityManager entityManager) { super(entityInformation, entityManager); - this.queryDslJpaRepositoryDelegate = new QueryDslJpaRepository((JpaEntityInformation) entityInformation, entityManager); + this.queryDslJpaRepositoryDelegate = new EntityGraphAwareQueryDslJpaRepository((JpaEntityInformation) entityInformation, entityManager); } public QueryDslJpaEntityGraphRepository(JpaEntityInformation entityInformation, EntityManager entityManager, EntityPathResolver resolver) { super(entityInformation, entityManager); - this.queryDslJpaRepositoryDelegate = new QueryDslJpaRepository(entityInformation, entityManager, resolver); + this.queryDslJpaRepositoryDelegate = new EntityGraphAwareQueryDslJpaRepository(entityInformation, entityManager, resolver); } @Override diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/QueryHintsUtils.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/QueryHintsUtils.java new file mode 100644 index 0000000..44a00c8 --- /dev/null +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/QueryHintsUtils.java @@ -0,0 +1,45 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.support; + +import javax.persistence.EntityManager; +import java.util.Map; + +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.query.Jpa21Utils; + +/** + * Created on 24/11/16. + * + * @author Reda.Housni-Alaoui + */ +class QueryHintsUtils { + /** + * @param queryHints + * @return True if the QueryHints already hold an EntityGraph + */ + static boolean containsEntityGraph(Map queryHints){ + return queryHints != null + && (queryHints.containsKey(EntityGraph.EntityGraphType.FETCH.getKey()) + || queryHints.containsKey(EntityGraph.EntityGraphType.LOAD.getKey())); + } + + /** + * Remove all EntityGraph pre existing in the QueryHints + * @param queryHints + */ + static void removeEntityGraphs(Map queryHints){ + if(queryHints == null){ + return; + } + queryHints.remove(EntityGraph.EntityGraphType.FETCH.getKey()); + queryHints.remove(EntityGraph.EntityGraphType.LOAD.getKey()); + } + + + static Map buildQueryHints(EntityManager entityManager, EntityGraphBean entityGraph) { + return Jpa21Utils.tryGetFetchGraphHints( + entityManager, + entityGraph.getJpaEntityGraph(), + entityGraph.getDomainClass() + ); + } +} diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/RepositoryEntityManagerEntityGraphInjector.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/RepositoryEntityManagerEntityGraphInjector.java index 3c3507c..852dde0 100644 --- a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/RepositoryEntityManagerEntityGraphInjector.java +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/RepositoryEntityManagerEntityGraphInjector.java @@ -2,12 +2,14 @@ import javax.persistence.EntityManager; import javax.persistence.Query; +import javax.persistence.criteria.CriteriaQuery; import java.util.*; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.aop.framework.ProxyFactory; -import org.springframework.data.jpa.repository.query.Jpa21Utils; /** * Injects captured {@link org.springframework.data.jpa.repository.query.JpaEntityGraph} into query hints.
@@ -19,6 +21,8 @@ */ class RepositoryEntityManagerEntityGraphInjector implements MethodInterceptor { + private static final Logger LOG = LoggerFactory.getLogger(RepositoryEntityManagerEntityGraphInjector.class); + /** * The list of methods that can take a map of query hints as an argument */ @@ -28,6 +32,9 @@ class RepositoryEntityManagerEntityGraphInjector implements MethodInterceptor { */ private static final List CREATE_QUERY_METHODS = Arrays.asList("createQuery", "createNamedQuery"); + private RepositoryEntityManagerEntityGraphInjector() { + } + /** * Builds a proxy on entity manager which is aware of methods that can make use of query hints. * @@ -42,48 +49,47 @@ static EntityManager proxy(EntityManager entityManager) { @Override public Object invoke(MethodInvocation invocation) throws Throwable { + EntityGraphBean entityGraphCandidate = RepositoryMethodEntityGraphExtractor.getCurrentJpaEntityGraph(); String methodName = invocation.getMethod().getName(); - if (FIND_METHODS.contains(methodName)) { - addEntityGraph(invocation); + boolean hasEntityGraphCandidate = entityGraphCandidate != null; + if (hasEntityGraphCandidate && FIND_METHODS.contains(methodName)) { + addEntityGraphToFindMethodQueryHints(entityGraphCandidate, invocation); } + Object result = invocation.proceed(); - if (CREATE_QUERY_METHODS.contains(methodName)) { - addEntityGraph(invocation, (Query) result); + + if (hasEntityGraphCandidate + && CREATE_QUERY_METHODS.contains(methodName) + && isQueryCreationEligible(entityGraphCandidate, invocation)) { + result = RepositoryQueryEntityGraphInjector.proxy((Query) result, (EntityManager) invocation.getThis(), entityGraphCandidate); } return result; } - private Map getCurrentQueryHints(EntityManager entityManager) { - EntityGraphBean entityGraphBean = RepositoryMethodEntityGraphExtractor.getCurrentJpaEntityGraph(); - if (entityGraphBean == null) { - return new HashMap(); + private boolean isQueryCreationEligible(EntityGraphBean entityGraphCandidate, MethodInvocation invocation) { + Class resultType = null; + for (Object argument : invocation.getArguments()) { + if (argument instanceof Class) { + resultType = (Class) argument; + break; + } else if (argument instanceof CriteriaQuery) { + resultType = ((CriteriaQuery) argument).getResultType(); + break; + } } - return Jpa21Utils.tryGetFetchGraphHints( - entityManager, - entityGraphBean.getJpaEntityGraph(), - entityGraphBean.getDomainClass() - ); + return resultType == null || resultType.equals(entityGraphCandidate.getDomainClass()); } - /** - * Push the current entity graph to the created query - * - * @param invocation The method invocation - * @param query The query to populate - */ - private void addEntityGraph(MethodInvocation invocation, Query query) { - Map hints = getCurrentQueryHints((EntityManager) invocation.getThis()); - for (Map.Entry hint : hints.entrySet()) { - query.setHint(hint.getKey(), hint.getValue()); - } - } /** * Push the current entity graph to the find method query hints. * + * @param entityGraphCandidate The EntityGraph to set * @param invocation The invocation of the find method */ - private void addEntityGraph(MethodInvocation invocation) { + private void addEntityGraphToFindMethodQueryHints(EntityGraphBean entityGraphCandidate, MethodInvocation invocation) { + LOG.trace("Trying to push the EntityGraph candidate to the query hints find method"); + Map queryProperties = null; int index = 0; for (Object argument : invocation.getArguments()) { @@ -94,11 +100,17 @@ private void addEntityGraph(MethodInvocation invocation) { index++; } if (queryProperties == null) { + LOG.trace("No query hints passed to the find method."); + return; + } + if (!entityGraphCandidate.isPrimary() && QueryHintsUtils.containsEntityGraph(queryProperties)) { + LOG.trace("The query hints passed with the find method already hold an entity graph. Overriding aborted because the candidate EntityGraph is optional."); return; } queryProperties = new HashMap(queryProperties); - queryProperties.putAll(getCurrentQueryHints((EntityManager) invocation.getThis())); + QueryHintsUtils.removeEntityGraphs(queryProperties); + queryProperties.putAll(QueryHintsUtils.buildQueryHints((EntityManager) invocation.getThis(), entityGraphCandidate)); invocation.getArguments()[index] = queryProperties; } } diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/RepositoryMethodEntityGraphExtractor.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/RepositoryMethodEntityGraphExtractor.java index d15b864..ed48895 100644 --- a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/RepositoryMethodEntityGraphExtractor.java +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/RepositoryMethodEntityGraphExtractor.java @@ -1,12 +1,21 @@ package com.cosium.spring.data.jpa.entity.graph.repository.support; +import javax.persistence.EntityManager; import java.util.List; import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraph; +import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraphUtils; +import com.cosium.spring.data.jpa.entity.graph.repository.exception.InapplicableEntityGraphException; +import com.cosium.spring.data.jpa.entity.graph.repository.exception.MultipleDefaultEntityGraphException; +import com.cosium.spring.data.jpa.entity.graph.repository.exception.MultipleEntityGraphException; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.aop.framework.ReflectiveMethodInvocation; import org.springframework.core.NamedThreadLocal; +import org.springframework.core.ResolvableType; import org.springframework.data.jpa.repository.query.JpaEntityGraph; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.support.RepositoryProxyPostProcessor; @@ -21,35 +30,152 @@ */ class RepositoryMethodEntityGraphExtractor implements RepositoryProxyPostProcessor { - private static final ThreadLocal CURRENT_ENTITY_GRAPH = - new NamedThreadLocal("Thread local holding the current spring data jpa repository entity graph"); + private static final Logger LOG = LoggerFactory.getLogger(RepositoryMethodEntityGraphExtractor.class); + + private static final ThreadLocal CURRENT_REPOSITORY = + new NamedThreadLocal("Thread local holding the current repository"); + + private final EntityManager entityManager; + + RepositoryMethodEntityGraphExtractor(EntityManager entityManager) { + this.entityManager = entityManager; + } @Override public void postProcess(ProxyFactory factory, RepositoryInformation repositoryInformation) { - factory.addAdvice(new JpaEntityGraphMethodInterceptor(repositoryInformation.getDomainType())); + factory.addAdvice(new JpaEntityGraphMethodInterceptor(entityManager, repositoryInformation.getDomainType())); } static EntityGraphBean getCurrentJpaEntityGraph() { - return CURRENT_ENTITY_GRAPH.get(); + JpaEntityGraphMethodInterceptor currentRepository = CURRENT_REPOSITORY.get(); + if (currentRepository == null) { + return null; + } + return currentRepository.getCurrentJpaEntityGraph(); + } + + /** + * @param entityGraph + * @return True if the provided EntityGraph is empty + */ + private static boolean isEmpty(EntityGraph entityGraph) { + return entityGraph == null || + ( + entityGraph.getEntityGraphAttributePaths() == null + && entityGraph.getEntityGraphName() == null + && entityGraph.getEntityGraphType() == null + ); } private static class JpaEntityGraphMethodInterceptor implements MethodInterceptor { + private static final String DEFAULT_ENTITYGRAPH_NAME_SUFFIX = ".default"; private final Class domainClass; + private final EntityGraph defaultEntityGraph; + private final ThreadLocal currentEntityGraph = new NamedThreadLocal("Thread local holding the current spring data jpa repository entity graph"); - JpaEntityGraphMethodInterceptor(Class domainClass) { + JpaEntityGraphMethodInterceptor(EntityManager entityManager, Class domainClass) { this.domainClass = domainClass; + this.defaultEntityGraph = findDefaultEntityGraph(entityManager); + } + + /** + * @param entityManager + * @return The default entity graph if it exists. Null otherwise. + */ + private EntityGraph findDefaultEntityGraph(EntityManager entityManager) { + EntityGraph defaultEntityGraph = null; + List> entityGraphs = (List>) entityManager.getEntityGraphs(domainClass); + for (javax.persistence.EntityGraph entityGraph : entityGraphs) { + if (entityGraph.getName().endsWith(DEFAULT_ENTITYGRAPH_NAME_SUFFIX)) { + if (defaultEntityGraph != null) { + throw new MultipleDefaultEntityGraphException(entityGraph.getName(), defaultEntityGraph.getEntityGraphName()); + } + defaultEntityGraph = EntityGraphUtils.fromName(entityGraph.getName(), true); + } + } + return defaultEntityGraph; + } + + EntityGraphBean getCurrentJpaEntityGraph() { + return currentEntityGraph.get(); + } + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + JpaEntityGraphMethodInterceptor oldRepo = CURRENT_REPOSITORY.get(); + CURRENT_REPOSITORY.set(this); + try { + return doInvoke(invocation); + } finally { + CURRENT_REPOSITORY.set(oldRepo); + } + } + + private Object doInvoke(MethodInvocation invocation) throws Throwable { + Object[] arguments = invocation.getArguments(); + EntityGraph providedEntityGraph = null; + for (Object argument : arguments) { + if (!(argument instanceof EntityGraph)) { + continue; + } + EntityGraph newEntityGraph = (EntityGraph) argument; + if (providedEntityGraph != null) { + throw new MultipleEntityGraphException("Duplicate EntityGraphs detected. '" + + providedEntityGraph + "' and '" + newEntityGraph + "' were passed to method " + invocation.getMethod()); + } + providedEntityGraph = newEntityGraph; + } + + Class implementationClass; + if (invocation instanceof ReflectiveMethodInvocation) { + implementationClass = ((ReflectiveMethodInvocation) invocation).getProxy().getClass(); + } else { + implementationClass = invocation.getThis().getClass(); + } + + EntityGraphBean entityGraphCandidate = buildEntityGraphCandidate( + providedEntityGraph, + ResolvableType.forMethodReturnType(invocation.getMethod(), implementationClass) + ); + + if (entityGraphCandidate != null && !entityGraphCandidate.isValid()) { + if (entityGraphCandidate.isOptional()) { + LOG.trace("Cannot apply EntityGraph {}", entityGraphCandidate); + entityGraphCandidate = null; + } else { + throw new InapplicableEntityGraphException("Cannot apply EntityGraph " + entityGraphCandidate + " to the the current query"); + } + } + + EntityGraphBean oldEntityGraphCandidate = currentEntityGraph.get(); + boolean newEntityGraphCandidatePreValidated = entityGraphCandidate != null && (oldEntityGraphCandidate == null || !oldEntityGraphCandidate.isPrimary()); + if (newEntityGraphCandidatePreValidated) { + currentEntityGraph.set(entityGraphCandidate); + } + try { + return invocation.proceed(); + } finally { + if (newEntityGraphCandidatePreValidated) { + currentEntityGraph.set(oldEntityGraphCandidate); + } + } } - private EntityGraphBean buildEntityGraphBean(EntityGraph entityGraph) { - if (entityGraph == null) { + private EntityGraphBean buildEntityGraphCandidate(EntityGraph providedEntityGraph, ResolvableType returnType) { + boolean isPrimary = true; + if (isEmpty(providedEntityGraph)) { + providedEntityGraph = defaultEntityGraph; + isPrimary = false; + } + if (providedEntityGraph == null) { return null; } - Assert.notNull(entityGraph.getType()); + Assert.notNull(providedEntityGraph.getEntityGraphType()); org.springframework.data.jpa.repository.EntityGraph.EntityGraphType type; - switch (entityGraph.getType()) { + switch (providedEntityGraph.getEntityGraphType()) { case FETCH: type = org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.FETCH; break; @@ -57,41 +183,17 @@ private EntityGraphBean buildEntityGraphBean(EntityGraph entityGraph) { type = org.springframework.data.jpa.repository.EntityGraph.EntityGraphType.LOAD; break; default: - throw new RuntimeException("Unknown entity graph type"); + throw new RuntimeException("Unexpected entity graph type '" + providedEntityGraph.getEntityGraphType() + "'"); } - List attributePaths = entityGraph.getAttributePaths(); + List attributePaths = providedEntityGraph.getEntityGraphAttributePaths(); JpaEntityGraph jpaEntityGraph = new JpaEntityGraph( - StringUtils.hasText(entityGraph.getName()) ? entityGraph.getName() : domainClass.getName() + "-_-_-_-_-_-", + StringUtils.hasText(providedEntityGraph.getEntityGraphName()) ? providedEntityGraph.getEntityGraphName() : domainClass.getName() + "-_-_-_-_-_-", type, attributePaths != null ? attributePaths.toArray(new String[attributePaths.size()]) : null ); - return new EntityGraphBean(jpaEntityGraph, domainClass); - } - - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - Object[] arguments = invocation.getArguments(); - EntityGraph entityGraph = null; - for (Object argument : arguments) { - if (!(argument instanceof EntityGraph)) { - continue; - } - entityGraph = (EntityGraph) argument; - break; - } - EntityGraphBean entityGraphBean = buildEntityGraphBean(entityGraph); - if (entityGraphBean != null) { - CURRENT_ENTITY_GRAPH.set(entityGraphBean); - } - try { - return invocation.proceed(); - } finally { - if (entityGraphBean != null) { - CURRENT_ENTITY_GRAPH.remove(); - } - } + return new EntityGraphBean(jpaEntityGraph, domainClass, returnType, providedEntityGraph.isOptional(), isPrimary); } } } diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/RepositoryQueryEntityGraphInjector.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/RepositoryQueryEntityGraphInjector.java new file mode 100644 index 0000000..0645b1a --- /dev/null +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/RepositoryQueryEntityGraphInjector.java @@ -0,0 +1,67 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.support; + +import javax.persistence.EntityManager; +import javax.persistence.Query; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.util.Assert; + +/** + * Created on 24/11/16. + * + * @author Reda.Housni-Alaoui + */ +class RepositoryQueryEntityGraphInjector implements MethodInterceptor { + + private static final Logger LOG = LoggerFactory.getLogger(RepositoryQueryEntityGraphInjector.class); + + private static final List EXECUTE_QUERY_METHODS = Arrays.asList("getResultList", "getSingleResult"); + + private final EntityManager entityManager; + private final EntityGraphBean entityGraphCandidate; + + private RepositoryQueryEntityGraphInjector(EntityManager entityManager, EntityGraphBean entityGraphCandidate) { + Assert.notNull(entityManager); + Assert.notNull(entityGraphCandidate); + this.entityManager = entityManager; + this.entityGraphCandidate = entityGraphCandidate; + } + + static Query proxy(Query query, EntityManager entityManager, EntityGraphBean entityGraphCandidate) { + ProxyFactory proxyFactory = new ProxyFactory(query); + proxyFactory.addAdvice(new RepositoryQueryEntityGraphInjector(entityManager, entityGraphCandidate)); + return (Query) proxyFactory.getProxy(); + } + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + if (EXECUTE_QUERY_METHODS.contains(invocation.getMethod().getName())) { + addEntityGraphToQuery((Query) invocation.getThis()); + } + return invocation.proceed(); + } + + private void addEntityGraphToQuery(Query query) { + if (CountQueryDetector.isCountQuery()) { + LOG.trace("CountQuery detected."); + return; + } + if (!entityGraphCandidate.isPrimary() && QueryHintsUtils.containsEntityGraph(query.getHints())) { + LOG.trace("The query hints passed with the find method already hold an entity graph. Overriding aborted because the candidate EntityGraph is optional."); + return; + } + + QueryHintsUtils.removeEntityGraphs(query.getHints()); + Map hints = QueryHintsUtils.buildQueryHints(entityManager, entityGraphCandidate); + for (Map.Entry hint : hints.entrySet()) { + query.setHint(hint.getKey(), hint.getValue()); + } + } +} diff --git a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/SimpleJpaEntityGraphRepository.java b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/SimpleJpaEntityGraphRepository.java index 1cc430c..098ebeb 100644 --- a/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/SimpleJpaEntityGraphRepository.java +++ b/src/main/java/com/cosium/spring/data/jpa/entity/graph/repository/support/SimpleJpaEntityGraphRepository.java @@ -2,9 +2,7 @@ import javax.persistence.EntityManager; import java.io.Serializable; -import java.util.HashMap; import java.util.List; -import java.util.Map; import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraph; import com.cosium.spring.data.jpa.entity.graph.repository.JpaEntityGraphRepository; @@ -14,8 +12,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.jpa.repository.query.Jpa21Utils; -import org.springframework.data.jpa.repository.query.JpaEntityGraph; import org.springframework.data.jpa.repository.support.JpaEntityInformation; import org.springframework.data.jpa.repository.support.SimpleJpaRepository; diff --git a/src/site/resources/settings.xml b/src/site/resources/settings.xml new file mode 100644 index 0000000..3756550 --- /dev/null +++ b/src/site/resources/settings.xml @@ -0,0 +1,27 @@ + + + + + + default + + + central + central + http://repo1.maven.org/maven2 + + + spring-snapshot-local + spring-snapshot-local + https://repo.spring.io/libs-snapshot-local + + + + + + + default + + diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/BaseTest.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/BaseTest.java index ab538bd..86ae4f0 100644 --- a/src/test/java/com/cosium/spring/data/jpa/entity/graph/BaseTest.java +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/BaseTest.java @@ -1,14 +1,11 @@ package com.cosium.spring.data.jpa.entity.graph; import com.github.springtestdbunit.DbUnitTestExecutionListener; -import com.github.springtestdbunit.annotation.DatabaseSetup; -import com.github.springtestdbunit.annotation.DatabaseTearDown; import org.junit.runner.RunWith; -import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureDataJpa; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import org.springframework.test.context.transaction.TransactionalTestExecutionListener; @@ -20,9 +17,8 @@ */ @TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, DbUnitTestExecutionListener.class}) -@RunWith(SpringRunner.class) +@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {DataRepositoryConfiguration.class}) -@AutoConfigureDataJpa @DirtiesContext public abstract class BaseTest { diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/DataRepositoryConfiguration.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/DataRepositoryConfiguration.java index ff5108f..f6bf6d4 100644 --- a/src/test/java/com/cosium/spring/data/jpa/entity/graph/DataRepositoryConfiguration.java +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/DataRepositoryConfiguration.java @@ -1,6 +1,8 @@ package com.cosium.spring.data.jpa.entity.graph; import com.cosium.spring.data.jpa.entity.graph.repository.support.JpaEntityGraphRepositoryFactoryBean; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @@ -11,6 +13,7 @@ */ @Configuration @EnableJpaRepositories(repositoryFactoryBeanClass = JpaEntityGraphRepositoryFactoryBean.class) +@EnableAutoConfiguration public class DataRepositoryConfiguration { } diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphCustomRepositoryTest.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphCustomRepositoryTest.java new file mode 100644 index 0000000..301e9d9 --- /dev/null +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphCustomRepositoryTest.java @@ -0,0 +1,48 @@ +package com.cosium.spring.data.jpa.entity.graph.repository; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.inject.Inject; + +import java.util.List; + +import com.cosium.spring.data.jpa.entity.graph.BaseTest; +import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraphUtils; +import com.cosium.spring.data.jpa.entity.graph.repository.sample.Product; +import com.cosium.spring.data.jpa.entity.graph.repository.sample.ProductRepository; +import com.github.springtestdbunit.annotation.DatabaseSetup; +import com.github.springtestdbunit.annotation.DatabaseTearDown; +import org.hibernate.Hibernate; +import org.junit.Test; +import org.springframework.transaction.annotation.Transactional; + +/** + * Created on 28/11/16. + * + * @author Reda.Housni-Alaoui + */ +@DatabaseSetup(BaseTest.DATASET) +@DatabaseTearDown +public class JpaEntityGraphCustomRepositoryTest extends BaseTest { + + @Inject + private ProductRepository productRepository; + + @Test + @Transactional + public void given_products_when_calling_customvoidmethod_with_eg_then_it_should_work(){ + productRepository.customMethod(EntityGraphUtils.fromName(Product.BRAND_EG)); + } + + @Test + @Transactional + public void given_products_when_calling_customMethodCallingAnotherRepository_with_eg_then_brand_should_be_loaded(){ + List products = productRepository.customMethodCallingAnotherRepository(EntityGraphUtils.fromName(Product.BRAND_EG)); + assertThat(products).isNotEmpty(); + for(Product product: products){ + assertThat(Hibernate.isInitialized(product.getBrand())).isTrue(); + } + } + + +} diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphQueryDslPredicateExecutorTest.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphQueryDslPredicateExecutorTest.java index 17b968d..2bdaa1a 100644 --- a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphQueryDslPredicateExecutorTest.java +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphQueryDslPredicateExecutorTest.java @@ -1,6 +1,6 @@ package com.cosium.spring.data.jpa.entity.graph.repository; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; import javax.inject.Inject; @@ -12,8 +12,11 @@ import com.cosium.spring.data.jpa.entity.graph.repository.sample.QProduct; import com.github.springtestdbunit.annotation.DatabaseSetup; import com.github.springtestdbunit.annotation.DatabaseTearDown; +import com.querydsl.core.types.Predicate; import org.hibernate.Hibernate; import org.junit.Test; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.transaction.annotation.Transactional; /** @@ -30,7 +33,7 @@ public class JpaEntityGraphQueryDslPredicateExecutorTest extends BaseTest { @Transactional @Test - public void given_null_eg_when_findone_then_then_brand_should_not_be_loaded(){ + public void given_null_eg_when_findone_then_brand_should_not_be_loaded(){ Product product = productRepository.findOne(QProduct.product.name.eq("Product 1"), (EntityGraph) null); assertThat(product).isNotNull(); assertThat(Hibernate.isInitialized(product.getBrand())).isFalse(); @@ -38,9 +41,20 @@ public void given_null_eg_when_findone_then_then_brand_should_not_be_loaded(){ @Transactional @Test - public void given_brand_eg_when_findone_then_then_brand_should_not_be_loaded(){ - Product product = productRepository.findOne(QProduct.product.name.eq("Product 1"), EntityGraphUtils.fromName(Product.PRODUCT_BRAND_EG)); + public void given_brand_eg_when_findone_then_brand_should_be_loaded(){ + Product product = productRepository.findOne(QProduct.product.name.eq("Product 1"), EntityGraphUtils.fromName(Product.BRAND_EG)); assertThat(product).isNotNull(); assertThat(Hibernate.isInitialized(product.getBrand())).isTrue(); } + + @Transactional + @Test + public void given_brand_eg_when_findpage_then_brand_should_be_loaded(){ + Page productPage = productRepository.findAll((Predicate) null, new PageRequest(0, 10),EntityGraphUtils.fromName(Product.BRAND_EG)); + assertThat(productPage.getContent()).isNotEmpty(); + for(Product product: productPage.getContent()){ + assertThat(Hibernate.isInitialized(product.getBrand())).isTrue(); + } + } + } diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphRepositoryTest.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphRepositoryTest.java index d03b7e3..3514f73 100644 --- a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphRepositoryTest.java +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphRepositoryTest.java @@ -1,6 +1,6 @@ package com.cosium.spring.data.jpa.entity.graph.repository; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; import javax.inject.Inject; import javax.persistence.criteria.CriteriaBuilder; @@ -12,14 +12,14 @@ import com.cosium.spring.data.jpa.entity.graph.BaseTest; import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraphUtils; -import com.cosium.spring.data.jpa.entity.graph.repository.sample.EntityGraphSpecification; -import com.cosium.spring.data.jpa.entity.graph.repository.sample.Product; -import com.cosium.spring.data.jpa.entity.graph.repository.sample.ProductRepository; +import com.cosium.spring.data.jpa.entity.graph.repository.exception.InapplicableEntityGraphException; +import com.cosium.spring.data.jpa.entity.graph.repository.sample.*; import com.github.springtestdbunit.annotation.DatabaseSetup; import com.github.springtestdbunit.annotation.DatabaseTearDown; import org.hibernate.Hibernate; import org.junit.Test; -import org.springframework.data.jpa.domain.Specification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.transaction.annotation.Transactional; /** @@ -33,6 +33,8 @@ public class JpaEntityGraphRepositoryTest extends BaseTest { @Inject private ProductRepository productRepository; + @Inject + private BrandRepository brandRepository; @Transactional @Test @@ -42,10 +44,26 @@ public void given_null_eg_when_findone_then_then_brand_should_not_be_loaded(){ assertThat(Hibernate.isInitialized(product.getBrand())).isFalse(); } + @Transactional + @Test + public void given_empty_eg_when_findone_then_then_brand_should_not_be_loaded(){ + Product product = productRepository.findOne(1L, EntityGraphUtils.empty()); + assertThat(product).isNotNull(); + assertThat(Hibernate.isInitialized(product.getBrand())).isFalse(); + } + @Transactional @Test public void given_brand_eg_when_findone_then_brand_should_be_loaded(){ - Product product = productRepository.findOne(1L, EntityGraphUtils.fromName(Product.PRODUCT_BRAND_EG)); + Product product = productRepository.findOne(1L, EntityGraphUtils.fromName(Product.BRAND_EG)); + assertThat(product).isNotNull(); + assertThat(Hibernate.isInitialized(product.getBrand())).isTrue(); + } + + @Transactional + @Test + public void given_optional_brand_eg_when_findone_then_brand_should_be_loaded(){ + Product product = productRepository.findOne(1L, EntityGraphUtils.fromName(Product.BRAND_EG, true)); assertThat(product).isNotNull(); assertThat(Hibernate.isInitialized(product.getBrand())).isTrue(); } @@ -53,11 +71,90 @@ public void given_brand_eg_when_findone_then_brand_should_be_loaded(){ @Transactional @Test public void given_brand_eg_when_findByName_then_brand_should_be_loaded(){ - List products = productRepository.findByName("Product 1", EntityGraphUtils.fromName(Product.PRODUCT_BRAND_EG)); + List products = productRepository.findByName("Product 1", EntityGraphUtils.fromName(Product.BRAND_EG)); assertThat(products).hasSize(1); for(Product product: products){ assertThat(Hibernate.isInitialized(product.getBrand())).isTrue(); } } + @Transactional + @Test + public void given_brand_eg_when_findAll_paginated_then_brand_should_be_loaded(){ + Page productPage = productRepository.findAll(new PageRequest(0, 10), EntityGraphUtils.fromName(Product.BRAND_EG)); + assertThat(productPage.getContent()).isNotEmpty(); + for(Product product: productPage.getContent()){ + assertThat(Hibernate.isInitialized(product.getBrand())).isTrue(); + } + } + + @Transactional + @Test + public void given_brand_eg_when_findAll_paginated_with_spec_then_brand_should_be_loaded(){ + Page productPage = productRepository.findAll(new EntityGraphSpecification(Product.BRAND_EG) { + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + return null; + } + }, new PageRequest(0, 10)); + + assertThat(productPage.getContent()).isNotEmpty(); + for(Product product: productPage.getContent()){ + assertThat(Hibernate.isInitialized(product.getBrand())).isTrue(); + } + } + + @Transactional + @Test + public void given_default_eg_when_findone_without_eg_then_supplier_should_be_loaded(){ + Product product = productRepository.findOne(1L); + assertThat(product).isNotNull(); + assertThat(Hibernate.isInitialized(product.getMaker())).isTrue(); + } + + @Transactional + @Test + public void given_default_eg_when_findByBarcode_with_eg_annotation_on_brand_eg_then_brand_should_be_loaded(){ + Product product = productRepository.findByBarcode("1111"); + assertThat(product).isNotNull(); + assertThat(Hibernate.isInitialized(product.getBrand())).isTrue(); + } + + @Transactional + @Test + public void given_products_when_countproductsbyname_then_it_should_work(){ + assertThat(productRepository.countByName("Product 1")).isEqualTo(1); + } + + @Transactional + @Test + public void given_products_when_findAllRaw_then_it_should_work(){ + assertThat(productRepository.findAllRaw()).isNotEmpty(); + } + + @Transactional + @Test + public void given_entity_without_default_eg_when_findall_then_it_should_work(){ + assertThat(brandRepository.findAll()).isNotEmpty(); + } + + @Transactional + @Test(expected = InapplicableEntityGraphException.class) + public void given_products_and_ProductName_projection_when_findProductNameByName_with_mandatory_eg_then_it_should_fail(){ + productRepository.findProductNameByName("Product 1", EntityGraphUtils.fromName(Product.BRAND_EG)); + } + + @Transactional + @Test + public void given_products_and_ProductName_projection_when_findProductNameByName_with_optional_eg_then_it_should_not_fail(){ + productRepository.findProductNameByName("Product 1", EntityGraphUtils.fromName(Product.BRAND_EG, true)); + } + + @Transactional + @Test + public void given_products_and_ProductName_projection_when_findProductNameByName_without_eg_then_it_should_work(){ + ProductName productName = productRepository.findProductNameByName("Product 1", EntityGraphUtils.empty()); + assertThat(productName).isNotNull(); + } + } \ No newline at end of file diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphSpecificationExecutorTest.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphSpecificationExecutorTest.java index 78d0b89..57d7034 100644 --- a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphSpecificationExecutorTest.java +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/JpaEntityGraphSpecificationExecutorTest.java @@ -10,6 +10,9 @@ import java.util.List; import com.cosium.spring.data.jpa.entity.graph.BaseTest; +import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraphUtils; +import com.cosium.spring.data.jpa.entity.graph.repository.exception.MultipleEntityGraphException; +import com.cosium.spring.data.jpa.entity.graph.repository.sample.EmptyEntityGraphSpecification; import com.cosium.spring.data.jpa.entity.graph.repository.sample.EntityGraphSpecification; import com.cosium.spring.data.jpa.entity.graph.repository.sample.Product; import com.cosium.spring.data.jpa.entity.graph.repository.sample.ProductRepository; @@ -35,7 +38,7 @@ public class JpaEntityGraphSpecificationExecutorTest extends BaseTest { @Transactional @Test public void given_brand_eg_when_findbyspecification_implementing_eg_then_brand_should_be_loaded() { - List products = productRepository.findAll((Specification) new EntityGraphSpecification(Product.PRODUCT_BRAND_EG) { + List products = productRepository.findAll((Specification) new EntityGraphSpecification(Product.BRAND_EG) { @Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { return cb.equal(root.get("name"), "Product 2"); @@ -46,4 +49,63 @@ public Predicate toPredicate(Root root, CriteriaQuery query, Criteri assertThat(Hibernate.isInitialized(product.getBrand())).isTrue(); } } + + @Transactional + @Test + public void given_brand_optional_eg_when_findbyspecification_implementing_eg_then_brand_should_be_loaded() { + List products = productRepository.findAll((Specification) new EntityGraphSpecification(Product.BRAND_EG, true) { + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + return cb.equal(root.get("name"), "Product 2"); + } + }); + assertThat(products).hasSize(1); + for (Product product : products) { + assertThat(Hibernate.isInitialized(product.getBrand())).isTrue(); + } + } + + @Transactional + @Test(expected = MultipleEntityGraphException.class) + public void given_products_when_findAllBySpec_with_two_non_empty_egs_then_it_should_fail() { + productRepository.findAll(new EntityGraphSpecification(Product.BRAND_EG) { + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + return null; + } + }, EntityGraphUtils.fromName(Product.BRAND_EG)); + } + + @Transactional + @Test(expected = MultipleEntityGraphException.class) + public void given_products_when_findAllBySpec_with_an_empty_eg_and_a_non_empty_one_then_it_should_fail() { + productRepository.findAll(new EntityGraphSpecification(Product.BRAND_EG) { + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + return null; + } + }, EntityGraphUtils.empty()); + } + + @Transactional + @Test(expected = MultipleEntityGraphException.class) + public void given_products_when_findAllBySpec_with_two_empty_eg_then_it_should_fail() { + productRepository.findAll(new EmptyEntityGraphSpecification() { + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + return null; + } + }, EntityGraphUtils.empty()); + } + + @Transactional + @Test(expected = MultipleEntityGraphException.class) + public void given_products_when_findAllBySpec_with_a_non_empty_eg_and_an_empty_one_then_it_should_fail() { + productRepository.findAll(new EmptyEntityGraphSpecification() { + @Override + public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { + return null; + } + }, EntityGraphUtils.fromName(Product.BRAND_EG)); + } } diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/Brand.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/Brand.java index eee7acf..4855a1d 100644 --- a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/Brand.java +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/Brand.java @@ -7,9 +7,14 @@ * * @author Reda.Housni-Alaoui */ +@NamedEntityGraphs(value = { + @NamedEntityGraph(name = Brand.EMPTY_EG, attributeNodes = {}) +}) @Entity public class Brand { + public static final String EMPTY_EG = "Brand.empty"; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Access(value = AccessType.PROPERTY) diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/BrandRepository.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/BrandRepository.java new file mode 100644 index 0000000..c785f0a --- /dev/null +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/BrandRepository.java @@ -0,0 +1,12 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.sample; + +import com.cosium.spring.data.jpa.entity.graph.repository.JpaEntityGraphRepository; + +/** + * Created on 24/11/16. + * + * @author Reda.Housni-Alaoui + */ +public interface BrandRepository extends JpaEntityGraphRepository { + +} diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/EmptyEntityGraphSpecification.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/EmptyEntityGraphSpecification.java new file mode 100644 index 0000000..f0bb1cd --- /dev/null +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/EmptyEntityGraphSpecification.java @@ -0,0 +1,38 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.sample; + +import java.util.List; + +import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraph; +import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraphType; +import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraphUtils; +import org.springframework.data.jpa.domain.Specification; + +/** + * Created on 27/11/16. + * + * @author Reda.Housni-Alaoui + */ +public abstract class EmptyEntityGraphSpecification implements Specification, EntityGraph { + + private final EntityGraph entityGraph = EntityGraphUtils.empty(); + + @Override + public EntityGraphType getEntityGraphType() { + return entityGraph.getEntityGraphType(); + } + + @Override + public String getEntityGraphName() { + return entityGraph.getEntityGraphName(); + } + + @Override + public List getEntityGraphAttributePaths() { + return entityGraph.getEntityGraphAttributePaths(); + } + + @Override + public boolean isOptional() { + return false; + } +} diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/EntityGraphSpecification.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/EntityGraphSpecification.java index 4b19361..8eaf9bc 100644 --- a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/EntityGraphSpecification.java +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/EntityGraphSpecification.java @@ -14,23 +14,34 @@ public abstract class EntityGraphSpecification implements Specification, EntityGraph { private final String entityGraphName; + private final boolean optional; - public EntityGraphSpecification(String entityGraphName) { + public EntityGraphSpecification(String entityGraphName, boolean optional) { this.entityGraphName = entityGraphName; + this.optional = optional; + } + + public EntityGraphSpecification(String entityGraphName) { + this(entityGraphName, false); } @Override - public String getName() { + public String getEntityGraphName() { return entityGraphName; } @Override - public EntityGraphType getType() { + public EntityGraphType getEntityGraphType() { return EntityGraphType.FETCH; } @Override - public List getAttributePaths() { + public List getEntityGraphAttributePaths() { return null; } + + @Override + public boolean isOptional() { + return optional; + } } diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/Maker.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/Maker.java new file mode 100644 index 0000000..3cf877a --- /dev/null +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/Maker.java @@ -0,0 +1,34 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.sample; + +import javax.persistence.*; + +/** + * Created on 24/11/16. + * + * @author Reda.Housni-Alaoui + */ +@Entity +public class Maker { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Access(value = AccessType.PROPERTY) + private long id = 0; + + private String name; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/Product.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/Product.java index 7d66d23..6e8f856 100644 --- a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/Product.java +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/Product.java @@ -8,14 +8,18 @@ * @author Reda.Housni-Alaoui */ @NamedEntityGraphs(value = { - @NamedEntityGraph(name = Product.PRODUCT_BRAND_EG, attributeNodes = { + @NamedEntityGraph(name = Product.DEFAULT_EG, attributeNodes = { + @NamedAttributeNode("maker") + }), + @NamedEntityGraph(name = Product.BRAND_EG, attributeNodes = { @NamedAttributeNode("brand") }) }) @Entity public class Product { - public static final String PRODUCT_BRAND_EG = "Product.brand"; + public static final String DEFAULT_EG = "Product.default"; + public static final String BRAND_EG = "Product.brand"; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -24,9 +28,14 @@ public class Product { private String name; + private String barcode; + @ManyToOne(fetch = FetchType.LAZY) private Brand brand; + @ManyToOne(fetch = FetchType.LAZY) + private Maker maker; + public long getId() { return id; } @@ -50,4 +59,20 @@ public Brand getBrand() { public void setBrand(Brand brand) { this.brand = brand; } + + public Maker getMaker() { + return maker; + } + + public void setMaker(Maker maker) { + this.maker = maker; + } + + public String getBarcode() { + return barcode; + } + + public void setBarcode(String barcode) { + this.barcode = barcode; + } } diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductName.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductName.java new file mode 100644 index 0000000..d6b88e1 --- /dev/null +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductName.java @@ -0,0 +1,10 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.sample; + +/** + * Created on 27/11/16. + * + * @author Reda.Housni-Alaoui + */ +public interface ProductName { + String getName(); +} diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductRepository.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductRepository.java index 02ec554..721bb06 100644 --- a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductRepository.java +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductRepository.java @@ -6,6 +6,7 @@ import com.cosium.spring.data.jpa.entity.graph.repository.JpaEntityGraphQueryDslPredicateExecutor; import com.cosium.spring.data.jpa.entity.graph.repository.JpaEntityGraphRepository; import com.cosium.spring.data.jpa.entity.graph.repository.JpaEntityGraphSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; /** * Created on 22/11/16. @@ -15,8 +16,20 @@ public interface ProductRepository extends JpaEntityGraphRepository, JpaEntityGraphSpecificationExecutor, - JpaEntityGraphQueryDslPredicateExecutor { + JpaEntityGraphQueryDslPredicateExecutor, + ProductRepositoryCustom { List findByName(String name, EntityGraph entityGraph); + List findByBrand(Brand brand); + + ProductName findProductNameByName(String name, EntityGraph entityGraph); + + @org.springframework.data.jpa.repository.EntityGraph(value = Product.BRAND_EG) + Product findByBarcode(String barcode); + + long countByName(String name); + + @Query("select p.name from Product p") + List findAllRaw(); } diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductRepositoryCustom.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductRepositoryCustom.java new file mode 100644 index 0000000..539d60f --- /dev/null +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductRepositoryCustom.java @@ -0,0 +1,18 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.sample; + +import java.util.List; + +import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraph; + +/** + * Created on 28/11/16. + * + * @author Reda.Housni-Alaoui + */ +public interface ProductRepositoryCustom { + + void customMethod(EntityGraph entityGraph); + + List customMethodCallingAnotherRepository(EntityGraph entityGraph); + +} diff --git a/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductRepositoryImpl.java b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductRepositoryImpl.java new file mode 100644 index 0000000..e5ab3e2 --- /dev/null +++ b/src/test/java/com/cosium/spring/data/jpa/entity/graph/repository/sample/ProductRepositoryImpl.java @@ -0,0 +1,39 @@ +package com.cosium.spring.data.jpa.entity.graph.repository.sample; + +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; + +import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraph; +import com.cosium.spring.data.jpa.entity.graph.domain.EntityGraphUtils; +import org.springframework.stereotype.Component; + +/** + * Created on 28/11/16. + * + * @author Reda.Housni-Alaoui + */ +@Component +public class ProductRepositoryImpl implements ProductRepositoryCustom { + + @Inject + private BrandRepository brandRepository; + @Inject + private ProductRepository productRepository; + @PersistenceContext + private EntityManager entityManager; + + @Override + public void customMethod(EntityGraph entityGraph) { + + } + + @Override + public List customMethodCallingAnotherRepository(EntityGraph entityGraph) { + Brand brand = brandRepository.findOne(1L, EntityGraphUtils.fromName(Brand.EMPTY_EG)); + entityManager.flush(); + entityManager.clear(); + return productRepository.findByBrand(brand); + } +} diff --git a/src/test/resources/com/cosium/spring/data/jpa/entity/graph/dataset.xml b/src/test/resources/com/cosium/spring/data/jpa/entity/graph/dataset.xml index 2c0be0f..a26fb53 100644 --- a/src/test/resources/com/cosium/spring/data/jpa/entity/graph/dataset.xml +++ b/src/test/resources/com/cosium/spring/data/jpa/entity/graph/dataset.xml @@ -3,8 +3,20 @@ - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..c75d693 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,12 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + \ No newline at end of file