diff --git a/src/main/java/th/co/geniustree/springdata/jpa/repository/JpaSpecificationExecutorWithProjection.java b/src/main/java/th/co/geniustree/springdata/jpa/repository/JpaSpecificationExecutorWithProjection.java index 2e2a3e4..81f8d4b 100644 --- a/src/main/java/th/co/geniustree/springdata/jpa/repository/JpaSpecificationExecutorWithProjection.java +++ b/src/main/java/th/co/geniustree/springdata/jpa/repository/JpaSpecificationExecutorWithProjection.java @@ -1,13 +1,14 @@ package th.co.geniustree.springdata.jpa.repository; -import java.io.Serializable; import org.springframework.data.domain.Page; 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.EntityGraph; import org.springframework.data.jpa.repository.query.JpaEntityGraph; import org.springframework.data.repository.NoRepositoryBean; +import java.util.List; import java.util.Optional; /** @@ -17,13 +18,18 @@ public interface JpaSpecificationExecutorWithProjection { Optional findById(ID id, Class projectionClass); - + Optional findOne(Specification spec, Class projectionClass); + List findAll(Specification spec, Class projectionClass); + + List findAll(Specification spec, Class projectionClass, Sort sort); + Page findAll(Specification spec, Class projectionClass, Pageable pageable); /** * Use Spring Data Annotation instead of manually provide EntityGraph. + * * @param spec * @param projectionType * @param namedEntityGraph @@ -37,6 +43,7 @@ public interface JpaSpecificationExecutorWithProjection { /** * Use Spring Data Annotation instead of manually provide EntityGraph. + * * @param spec * @param projectionClass * @param dynamicEntityGraph diff --git a/src/main/java/th/co/geniustree/springdata/jpa/repository/support/JpaSpecificationExecutorWithProjectionImpl.java b/src/main/java/th/co/geniustree/springdata/jpa/repository/support/JpaSpecificationExecutorWithProjectionImpl.java index 4c28c73..82e7a29 100644 --- a/src/main/java/th/co/geniustree/springdata/jpa/repository/support/JpaSpecificationExecutorWithProjectionImpl.java +++ b/src/main/java/th/co/geniustree/springdata/jpa/repository/support/JpaSpecificationExecutorWithProjectionImpl.java @@ -24,8 +24,25 @@ import org.springframework.util.Assert; import th.co.geniustree.springdata.jpa.repository.JpaSpecificationExecutorWithProjection; -import javax.persistence.*; -import javax.persistence.criteria.*; +import javax.persistence.EntityManager; +import javax.persistence.LockModeType; +import javax.persistence.ManyToOne; +import javax.persistence.NoResultException; +import javax.persistence.OneToOne; +import javax.persistence.Query; +import javax.persistence.Tuple; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Expression; +import javax.persistence.criteria.Fetch; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Path; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Root; +import javax.persistence.criteria.Selection; import javax.persistence.metamodel.Attribute; import javax.persistence.metamodel.Bindable; import javax.persistence.metamodel.ManagedType; @@ -34,9 +51,19 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Member; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; -import static javax.persistence.metamodel.Attribute.PersistentAttributeType.*; +import static javax.persistence.metamodel.Attribute.PersistentAttributeType.ELEMENT_COLLECTION; +import static javax.persistence.metamodel.Attribute.PersistentAttributeType.MANY_TO_MANY; +import static javax.persistence.metamodel.Attribute.PersistentAttributeType.MANY_TO_ONE; +import static javax.persistence.metamodel.Attribute.PersistentAttributeType.ONE_TO_MANY; +import static javax.persistence.metamodel.Attribute.PersistentAttributeType.ONE_TO_ONE; /** @@ -46,7 +73,8 @@ public class JpaSpecificationExecutorWithProjectionImpl> ASSOCIATION_TYPES; - static{ + + static { Map> persistentAttributeTypes = new HashMap>(); persistentAttributeTypes.put(ONE_TO_ONE, OneToOne.class); persistentAttributeTypes.put(ONE_TO_MANY, null); @@ -56,6 +84,7 @@ public class JpaSpecificationExecutorWithProjectionImpl Optional findById(ID id, Class projectionType) { final ReturnedType returnedType = ReturnTypeWarpper.of(projectionType, getDomainClass(), projectionFactory); - + CriteriaBuilder builder = this.entityManager.getCriteriaBuilder(); CriteriaQuery q = builder.createQuery(Tuple.class); Root root = q.from(getDomainClass()); @@ -89,11 +118,11 @@ public Optional findById(ID id, Class projectionType) { } else { throw new IllegalArgumentException("only except projection"); } - + final TypedQuery query = this.applyRepositoryMethodMetadata(this.entityManager.createQuery(q)); - + try { - final MyResultProcessor resultProcessor = new MyResultProcessor(projectionFactory,returnedType); + final MyResultProcessor resultProcessor = new MyResultProcessor(projectionFactory, returnedType); final R singleResult = resultProcessor.processResult(query.getSingleResult(), new TupleConverter(returnedType)); return Optional.ofNullable(singleResult); } catch (NoResultException e) { @@ -107,7 +136,7 @@ public Optional findOne(Specification spec, Class projectionType) { final ReturnedType returnedType = ReturnTypeWarpper.of(projectionType, getDomainClass(), projectionFactory); final TypedQuery query = getTupleQuery(spec, Sort.unsorted(), returnedType); try { - final MyResultProcessor resultProcessor = new MyResultProcessor(projectionFactory,returnedType); + final MyResultProcessor resultProcessor = new MyResultProcessor(projectionFactory, returnedType); final R singleResult = resultProcessor.processResult(query.getSingleResult(), new TupleConverter(returnedType)); return Optional.ofNullable(singleResult); } catch (NoResultException e) { @@ -115,13 +144,28 @@ public Optional findOne(Specification spec, Class projectionType) { } } + @Override + public List findAll(Specification spec, Class projectionType) { + return findAll(spec, projectionType, Sort.unsorted()); + } + + @Override + public List findAll(Specification spec, Class projectionType, Sort sort) { + return findAll(spec, projectionType, Pageable.unpaged(), sort).getContent(); + } + @Override public Page findAll(Specification spec, Class projectionType, Pageable pageable) { + Sort sort = pageable.getSort() != null && pageable.getSort().isSorted() ? pageable.getSort() : Sort.unsorted(); + return findAll(spec, projectionType, pageable, sort); + } + + private Page findAll(Specification spec, Class projectionType, Pageable pageable, Sort sort) { final ReturnedType returnedType = ReturnTypeWarpper.of(projectionType, getDomainClass(), projectionFactory); - final TypedQuery query = getTupleQuery(spec, pageable.getSort() != null && pageable.getSort().isSorted() ? pageable.getSort() : Sort.unsorted(), returnedType); - final MyResultProcessor resultProcessor = new MyResultProcessor(projectionFactory,returnedType); + final TypedQuery query = getTupleQuery(spec, sort, returnedType); + final MyResultProcessor resultProcessor = new MyResultProcessor(projectionFactory, returnedType); if (pageable.isPaged()) { - query.setFirstResult((int)pageable.getOffset()); + query.setFirstResult((int) pageable.getOffset()); query.setMaxResults(pageable.getPageSize()); } final List resultList = resultProcessor.processResult(query.getResultList(), new TupleConverter(returnedType)); @@ -135,8 +179,8 @@ static Long executeCountQuery(TypedQuery query) { Long total = 0L; Long element; - for(Iterator var3 = totals.iterator(); var3.hasNext(); total = total + (element == null ? 0L : element)) { - element = (Long)var3.next(); + for (Iterator var3 = totals.iterator(); var3.hasNext(); total = total + (element == null ? 0L : element)) { + element = (Long) var3.next(); } return total; @@ -144,17 +188,17 @@ static Long executeCountQuery(TypedQuery query) { @Override public Page findAll(Specification spec, Class projectionType, String namedEntityGraph, org.springframework.data.jpa.repository.EntityGraph.EntityGraphType type, Pageable pageable) { - return findAll(spec,projectionType,pageable); + return findAll(spec, projectionType, pageable); } @Override public Page findAll(Specification spec, Class projectionType, JpaEntityGraph dynamicEntityGraph, Pageable pageable) { - return findAll(spec,projectionType,pageable); + return findAll(spec, projectionType, pageable); } protected TypedQuery getTupleQuery(@Nullable Specification spec, Sort sort, ReturnedType returnedType) { - if (!returnedType.needsCustomConstruction()){ - return getQuery(spec,sort); + if (!returnedType.needsCustomConstruction()) { + return getQuery(spec, sort); } CriteriaBuilder builder = this.entityManager.getCriteriaBuilder(); CriteriaQuery query = builder.createQuery(Tuple.class); @@ -213,9 +257,10 @@ private TypedQuery applyRepositoryMethodMetadata(TypedQuery query) { return toReturn; } + private void applyQueryHints(Query query) { QueryHints queryHints = DefaultQueryHints.of(this.entityInformation, getRepositoryMethodMetadata()); - if(queryHints==null){ + if (queryHints == null) { queryHints = QueryHints.NoHints.INSTANCE; } for (Map.Entry hint : queryHints.withFetchGraphs(this.entityManager)) { @@ -311,6 +356,7 @@ private static boolean requiresJoin(@Nullable Bindable propertyPathModel, boo return from.join(attribute, JoinType.LEFT); } + private static boolean isAlreadyFetched(From from, String attribute) { for (Fetch fetch : from.getFetches()) { diff --git a/src/test/java/th/co/geniustree/springdata/jpa/SpecificationExecutorProjectionTest.java b/src/test/java/th/co/geniustree/springdata/jpa/SpecificationExecutorProjectionTest.java index 9f70e17..361262e 100644 --- a/src/test/java/th/co/geniustree/springdata/jpa/SpecificationExecutorProjectionTest.java +++ b/src/test/java/th/co/geniustree/springdata/jpa/SpecificationExecutorProjectionTest.java @@ -8,6 +8,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Transactional; @@ -15,6 +16,7 @@ import th.co.geniustree.springdata.jpa.repository.DocumentRepository; import th.co.geniustree.springdata.jpa.specification.DocumentSpecs; +import java.util.List; import java.util.Optional; @RunWith(SpringRunner.class) @@ -22,62 +24,170 @@ @DataJpaTest @Transactional public class SpecificationExecutorProjectionTest { + @Autowired private DocumentRepository documentRepository; - @Test public void findAll() { Specification where = Specification.where(DocumentSpecs.idEq(1L)); - Page all = documentRepository.findAll(where, DocumentRepository.DocumentWithoutParent.class, PageRequest.of(0,10)); + + Page all = documentRepository.findAll( + where, DocumentRepository.DocumentWithoutParent.class, PageRequest.of(0, 10) + ); Assertions.assertThat(all).isNotEmpty(); Assertions.assertThat(all.getContent().get(0).getDocumentType()).isEqualTo("ต้นฉบับ"); + + List allList = documentRepository.findAll( + where, DocumentRepository.DocumentWithoutParent.class + ); + Assertions.assertThat(allList).isNotEmpty(); + Assertions.assertThat(allList.get(0).getDocumentType()).isEqualTo("ต้นฉบับ"); + + Sort sortByDescription = Sort.by(Sort.Order.desc("description")); + List allListSorted = documentRepository.findAll( + where, DocumentRepository.DocumentWithoutParent.class, sortByDescription + ); + Assertions.assertThat(allListSorted).isNotEmpty(); + Assertions.assertThat(allListSorted.get(0).getDocumentType()).isEqualTo("ต้นฉบับ"); } @Test public void findAll2() { Specification where = Specification.where(DocumentSpecs.idEq(1L)); - Page all = documentRepository.findAll(where, DocumentRepository.DocumentWithoutParent.class, PageRequest.of(0,10)); + + Page all = documentRepository.findAll( + where, DocumentRepository.DocumentWithoutParent.class, PageRequest.of(0, 10) + ); Assertions.assertThat(all).isNotEmpty(); Assertions.assertThat(all.getContent().get(0).getChild().size()).isEqualTo(1); + + List allList = documentRepository.findAll( + where, DocumentRepository.DocumentWithoutParent.class + ); + Assertions.assertThat(allList).isNotEmpty(); + Assertions.assertThat(allList.get(0).getChild().size()).isEqualTo(1); + + Sort sortByDescription = Sort.by(Sort.Order.desc("description")); + List allListSorted = documentRepository.findAll( + where, DocumentRepository.DocumentWithoutParent.class, sortByDescription + ); + Assertions.assertThat(allListSorted).isNotEmpty(); + Assertions.assertThat(allListSorted.get(0).getChild().size()).isEqualTo(1); } @Test public void findAll3() { Specification where = Specification.where(DocumentSpecs.idEq(1L)); - Page all = documentRepository.findAll(where, DocumentRepository.OnlyId.class, PageRequest.of(0,10)); + + Page all = documentRepository.findAll( + where, DocumentRepository.OnlyId.class, PageRequest.of(0, 10) + ); Assertions.assertThat(all).isNotEmpty(); Assertions.assertThat(all.getContent().get(0).getId()).isEqualTo(1L); + + List allList = documentRepository.findAll( + where, DocumentRepository.OnlyId.class + ); + Assertions.assertThat(allList).isNotEmpty(); + Assertions.assertThat(allList.get(0).getId()).isEqualTo(1L); + + Sort sortByDescription = Sort.by(Sort.Order.desc("description")); + List allListSorted = documentRepository.findAll( + where, DocumentRepository.OnlyId.class, sortByDescription + ); + Assertions.assertThat(allListSorted).isNotEmpty(); + Assertions.assertThat(allListSorted.get(0).getId()).isEqualTo(1L); } @Test public void findAll4() { Specification where = Specification.where(DocumentSpecs.idEq(24L)); - Page all = documentRepository.findAll(where, DocumentRepository.DocumentWithoutParent.class, PageRequest.of(0,10)); + + Page all = documentRepository.findAll( + where, DocumentRepository.DocumentWithoutParent.class, PageRequest.of(0, 10) + ); Assertions.assertThat(all).isNotEmpty(); Assertions.assertThat(all.getContent().get(0).getChild()).isNull(); + + List allList = documentRepository.findAll( + where, DocumentRepository.DocumentWithoutParent.class + ); + Assertions.assertThat(allList).isNotEmpty(); + Assertions.assertThat(allList.get(0).getChild()).isNull(); + + Sort sortByDescription = Sort.by(Sort.Order.desc("description")); + List allListSorted = documentRepository.findAll( + where, DocumentRepository.DocumentWithoutParent.class, sortByDescription + ); + Assertions.assertThat(allListSorted).isNotEmpty(); + Assertions.assertThat(allListSorted.get(0).getChild()).isNull(); } @Test public void findAll5() { Specification where = Specification.where(DocumentSpecs.idEq(24L)); - Page all = documentRepository.findAll(where, DocumentRepository.OnlyParent.class, PageRequest.of(0,10)); + + Page all = documentRepository.findAll( + where, DocumentRepository.OnlyParent.class, PageRequest.of(0, 10) + ); Assertions.assertThat(all).isNotEmpty(); Assertions.assertThat(all.getContent().get(0).getParent().getId()).isEqualTo(13L); + + List allList = documentRepository.findAll( + where, DocumentRepository.OnlyParent.class + ); + Assertions.assertThat(allList).isNotEmpty(); + Assertions.assertThat(allList.get(0).getParent().getId()).isEqualTo(13L); + + Sort sortByDescription = Sort.by(Sort.Order.desc("description")); + List allListSorted = documentRepository.findAll( + where, DocumentRepository.OnlyParent.class, sortByDescription + ); + Assertions.assertThat(allListSorted).isNotEmpty(); + Assertions.assertThat(allListSorted.get(0).getParent().getId()).isEqualTo(13L); + } + + @Test + public void findAllListUnsorted() { + Specification where = Specification.where(DocumentSpecs.descriptionLike("description1%")); + + List all = documentRepository.findAll( + where, DocumentRepository.DocumentWithoutChild.class + ); + Assertions.assertThat(all) + .hasSize(3) + .extracting("description") + .containsExactlyInAnyOrder("description10", "description11", "description12"); } + @Test - public void find_single_page() { + public void findAllListSorted() { + Specification where = Specification.where(DocumentSpecs.descriptionLike("description1%")); + + Sort sortByDescription = Sort.by(Sort.Order.desc("description")); + List all = documentRepository.findAll( + where, DocumentRepository.DocumentWithoutChild.class, sortByDescription + ); + Assertions.assertThat(all) + .hasSize(3) + .extracting("description") + .containsExactly("description12", "description11", "description10"); + } + + @Test + public void findSinglePage() { Specification where = Specification.where(DocumentSpecs.idEq(24L)); - Page all = documentRepository.findAll(where, DocumentRepository.OnlyParent.class, PageRequest.of(0,10)); + Page all = documentRepository.findAll(where, DocumentRepository.OnlyParent.class, PageRequest.of(0, 10)); Assertions.assertThat(all).isNotEmpty(); Assertions.assertThat(all.getTotalElements()).isEqualTo(1); Assertions.assertThat(all.getTotalPages()).isEqualTo(1); } @Test - public void find_all_page() { + public void findAllPage() { Specification where = Specification.where(null); - Page all = documentRepository.findAll(where, DocumentRepository.OnlyParent.class, PageRequest.of(0,10)); + Page all = documentRepository.findAll(where, DocumentRepository.OnlyParent.class, PageRequest.of(0, 10)); Assertions.assertThat(all).isNotEmpty(); Assertions.assertThat(all.getTotalElements()).isEqualTo(24); Assertions.assertThat(all.getTotalPages()).isEqualTo(3); @@ -89,13 +199,13 @@ public void findOne() { Optional one = documentRepository.findOne(where, DocumentRepository.DocumentWithoutParent.class); Assertions.assertThat(one.get().getDocumentType()).isEqualTo("ต้นฉบับ"); } - + @Test public void findBydId() { Optional one = documentRepository.findById(1L, DocumentRepository.DocumentWithoutParent.class); Assertions.assertThat(one.get().getDocumentType()).isEqualTo("ต้นฉบับ"); } - + @Test public void findOneWithOpenProjection() { Specification where = Specification.where(DocumentSpecs.idEq(1L)); @@ -106,7 +216,7 @@ public void findOneWithOpenProjection() { @Test public void findAllWithOpenProjection() { Specification where = Specification.where(DocumentSpecs.idEq(1L)); - Page page = documentRepository.findAll(where, DocumentRepository.OpenProjection.class,PageRequest.of(0,10)); + Page page = documentRepository.findAll(where, DocumentRepository.OpenProjection.class, PageRequest.of(0, 10)); Assertions.assertThat(page.getContent().get(0).getDescriptionString()).isEqualTo("descriptiontest"); } diff --git a/src/test/java/th/co/geniustree/springdata/jpa/repository/DocumentRepository.java b/src/test/java/th/co/geniustree/springdata/jpa/repository/DocumentRepository.java index f670dd5..ecbdfed 100644 --- a/src/test/java/th/co/geniustree/springdata/jpa/repository/DocumentRepository.java +++ b/src/test/java/th/co/geniustree/springdata/jpa/repository/DocumentRepository.java @@ -10,25 +10,35 @@ * Created by pramoth on 9/28/2016 AD. */ public interface DocumentRepository extends JpaRepository,JpaSpecificationExecutorWithProjection { - public List findByParentIsNull(); - public static interface DocumentWithoutParent{ + List findByParentIsNull(); + + interface DocumentWithoutParent{ Long getId(); String getDescription(); String getDocumentType(); String getDocumentCategory(); List getChild(); } - public static interface OnlyId{ + + interface DocumentWithoutChild{ Long getId(); + String getDescription(); + String getDocumentType(); + String getDocumentCategory(); } - public static interface OnlyParent extends OnlyId{ + interface OnlyId{ + Long getId(); + } + + interface OnlyParent extends OnlyId{ OnlyId getParent(); } - public static interface OpenProjection extends OnlyId{ + interface OpenProjection extends OnlyId{ @Value("#{target.description}") String getDescriptionString(); } + } diff --git a/src/test/java/th/co/geniustree/springdata/jpa/specification/DocumentSpecs.java b/src/test/java/th/co/geniustree/springdata/jpa/specification/DocumentSpecs.java index f30211f..9bc5898 100644 --- a/src/test/java/th/co/geniustree/springdata/jpa/specification/DocumentSpecs.java +++ b/src/test/java/th/co/geniustree/springdata/jpa/specification/DocumentSpecs.java @@ -8,7 +8,13 @@ * Created by pramoth on 9/29/2016 AD. */ public class DocumentSpecs { - public static Specification idEq(Long id){ - return (root, query, cb) -> cb.equal(root.get(Document_.id),id); + + public static Specification idEq(Long id) { + return (root, query, cb) -> cb.equal(root.get(Document_.id), id); + } + + public static Specification descriptionLike(String descriptionLike) { + return (root, query, cb) -> cb.like(root.get(Document_.description), descriptionLike); } + }