Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
# specification-with-projection
Support Projections with `JpaSpecificationExecutor.findAll(Specification,Pageable)` for Spring Data JPA

>version 3.x.x for Spring Data JPA 3.x (Spring Boot 3.x)

>version 2.x.x for Spring Data JPA 2.x (Spring Boot 2.x)

>version 1.x.x Spring Data JPA 1.x

## How to use
* add dependency to pom
```xml
<!-- for Spring Data 3.x -->
<dependency>
<groupId>th.co.geniustree.springdata.jpa</groupId>
<artifactId>specification-with-projections</artifactId>
<version>3.0.0</version>
</dependency>
```
```xml
<!-- for Spring Data 2.x -->
<dependency>
<groupId>th.co.geniustree.springdata.jpa</groupId>
Expand Down
19 changes: 11 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>th.co.geniustree.springdata.jpa</groupId>
Expand All @@ -13,14 +14,15 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
<relativePath /> <!-- lookup parent from repository -->
<version>3.1.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<licenses>
<license>
Expand Down Expand Up @@ -73,12 +75,13 @@
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>6.3.1.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
</dependencies>
Expand Down Expand Up @@ -112,7 +115,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-sources</id>
Expand All @@ -125,7 +128,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.4</version>
<version>3.6.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
Expand All @@ -138,7 +141,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<version>3.1.0</version>
<executions>
<execution>
<id>sign-artifacts</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import javax.persistence.Tuple;
import javax.persistence.TupleElement;
import jakarta.persistence.Tuple;
import jakarta.persistence.TupleElement;
import java.util.*;
import java.util.stream.Collectors;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
public interface JpaSpecificationExecutorWithProjection<T, ID> {

<R> Optional<R> findById(ID id, Class<R> projectionClass);

<R> Optional<R> findOne(Specification<T> spec, Class<R> projectionClass);

<R> Page<R> findAll(Specification<T> spec, Class<R> projectionClass, Pageable pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import org.springframework.data.util.Optionals;
import org.springframework.util.Assert;

import javax.persistence.EntityManager;
import jakarta.persistence.EntityManager;
import java.util.*;
import java.util.function.BiConsumer;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,24 @@
import org.springframework.data.repository.query.ReturnTypeWarpper;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.TupleConverter;
import org.springframework.data.repository.support.PageableExecutionUtils;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import th.co.geniustree.springdata.jpa.repository.JpaSpecificationExecutorWithProjection;

import javax.persistence.*;
import javax.persistence.criteria.*;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Bindable;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.PluralAttribute;
import jakarta.persistence.*;
import jakarta.persistence.criteria.*;
import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.Bindable;
import jakarta.persistence.metamodel.ManagedType;
import jakarta.persistence.metamodel.PluralAttribute;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Member;
import java.util.*;

import static javax.persistence.metamodel.Attribute.PersistentAttributeType.*;
import static jakarta.persistence.metamodel.Attribute.PersistentAttributeType.*;


/**
Expand Down Expand Up @@ -68,11 +68,11 @@ public JpaSpecificationExecutorWithProjectionImpl(JpaEntityInformation entityInf
this.entityManager = entityManager;
this.entityInformation = entityInformation;
}

@Override
public <R> Optional<R> findById(ID id, Class<R> projectionType) {
final ReturnedType returnedType = ReturnTypeWarpper.of(projectionType, getDomainClass(), projectionFactory);

CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> q = builder.createQuery(Tuple.class);
Root<T> root = q.from(getDomainClass());
Expand All @@ -90,9 +90,9 @@ public <R> Optional<R> findById(ID id, Class<R> projectionType) {
} else {
throw new IllegalArgumentException("only except projection");
}

final TypedQuery<Tuple> query = this.applyRepositoryMethodMetadata(this.entityManager.createQuery(q));

try {
final MyResultProcessor resultProcessor = new MyResultProcessor(projectionFactory,returnedType);
final R singleResult = resultProcessor.processResult(query.getSingleResult(), new TupleConverter(returnedType));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
Expand All @@ -19,6 +20,7 @@
@ExtendWith(SpringExtension.class)
@DataJpaTest
@Transactional
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class SpecificationExecutorProjectionTest {
@Autowired
private DocumentRepository documentRepository;
Expand All @@ -27,46 +29,47 @@ public class SpecificationExecutorProjectionTest {
@Test
public void findAll() {
Specification<Document> where = Specification.where(DocumentSpecs.idEq(1L));
Page<DocumentRepository.DocumentWithoutParent> all = documentRepository.findAll(where, DocumentRepository.DocumentWithoutParent.class, PageRequest.of(0,10));
Page<DocumentRepository.DocumentWithoutParent> all = documentRepository.findAll(where, DocumentRepository.DocumentWithoutParent.class, PageRequest.of(0, 10));
Assertions.assertThat(all).isNotEmpty();
Assertions.assertThat(all.getContent().get(0).getDocumentType()).isEqualTo("ต้นฉบับ");
}

@Test
public void findAll2() {
Specification<Document> where = Specification.where(DocumentSpecs.idEq(1L));
Page<DocumentRepository.DocumentWithoutParent> all = documentRepository.findAll(where, DocumentRepository.DocumentWithoutParent.class, PageRequest.of(0,10));
Page<DocumentRepository.DocumentWithoutParent> 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);
}

@Test
public void findAll3() {
Specification<Document> where = Specification.where(DocumentSpecs.idEq(1L));
Page<DocumentRepository.OnlyId> all = documentRepository.findAll(where, DocumentRepository.OnlyId.class, PageRequest.of(0,10));
Page<DocumentRepository.OnlyId> all = documentRepository.findAll(where, DocumentRepository.OnlyId.class, PageRequest.of(0, 10));
Assertions.assertThat(all).isNotEmpty();
Assertions.assertThat(all.getContent().get(0).getId()).isEqualTo(1L);
}

@Test
public void findAll4() {
Specification<Document> where = Specification.where(DocumentSpecs.idEq(24L));
Page<DocumentRepository.DocumentWithoutParent> all = documentRepository.findAll(where, DocumentRepository.DocumentWithoutParent.class, PageRequest.of(0,10));
Page<DocumentRepository.DocumentWithoutParent> all = documentRepository.findAll(where, DocumentRepository.DocumentWithoutParent.class, PageRequest.of(0, 10));
Assertions.assertThat(all).isNotEmpty();
Assertions.assertThat(all.getContent().get(0).getChild()).isNull();
}

@Test
public void findAll5() {
Specification<Document> where = Specification.where(DocumentSpecs.idEq(24L));
Page<DocumentRepository.OnlyParent> all = documentRepository.findAll(where, DocumentRepository.OnlyParent.class, PageRequest.of(0,10));
Page<DocumentRepository.OnlyParent> 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);
}

@Test
public void find_single_page() {
Specification<Document> where = Specification.where(DocumentSpecs.idEq(24L));
Page<DocumentRepository.OnlyParent> all = documentRepository.findAll(where, DocumentRepository.OnlyParent.class, PageRequest.of(0,10));
Page<DocumentRepository.OnlyParent> 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);
Expand All @@ -75,7 +78,7 @@ public void find_single_page() {
@Test
public void find_all_page() {
Specification<Document> where = Specification.where(null);
Page<DocumentRepository.OnlyParent> all = documentRepository.findAll(where, DocumentRepository.OnlyParent.class, PageRequest.of(0,10));
Page<DocumentRepository.OnlyParent> 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);
Expand All @@ -87,13 +90,13 @@ public void findOne() {
Optional<DocumentRepository.DocumentWithoutParent> one = documentRepository.findOne(where, DocumentRepository.DocumentWithoutParent.class);
Assertions.assertThat(one.get().getDocumentType()).isEqualTo("ต้นฉบับ");
}

@Test
public void findBydId() {
Optional<DocumentRepository.DocumentWithoutParent> one = documentRepository.findById(1L, DocumentRepository.DocumentWithoutParent.class);
Assertions.assertThat(one.get().getDocumentType()).isEqualTo("ต้นฉบับ");
}

@Test
public void findOneWithOpenProjection() {
Specification<Document> where = Specification.where(DocumentSpecs.idEq(1L));
Expand All @@ -104,7 +107,7 @@ public void findOneWithOpenProjection() {
@Test
public void findAllWithOpenProjection() {
Specification<Document> where = Specification.where(DocumentSpecs.idEq(1L));
Page<DocumentRepository.OpenProjection> page = documentRepository.findAll(where, DocumentRepository.OpenProjection.class,PageRequest.of(0,10));
Page<DocumentRepository.OpenProjection> page = documentRepository.findAll(where, DocumentRepository.OpenProjection.class, PageRequest.of(0, 10));
Assertions.assertThat(page.getContent().get(0).getDescriptionString()).isEqualTo("descriptiontest");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package th.co.geniustree.springdata.jpa.domain;

import javax.persistence.*;
import jakarta.persistence.*;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package th.co.geniustree.springdata.jpa.domain;

import javax.persistence.*;
import jakarta.persistence.*;

import java.io.Serializable;

/**
Expand Down
4 changes: 2 additions & 2 deletions src/test/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
################## JPA
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
spring.jpa.hibernate.use-new-id-generator-mappings=true
spring.jpa.properties.hibernate.id.new_generator_mappings=true
spring.sql.init.mode=always
################## spring.datasource
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_ON_EXIT=FALSE;MODE=LEGACY
# spring.datasource.username=sa
# spring.datasource.password=sa
spring.datasource.driver-class-name=org.h2.Driver
Expand Down
12 changes: 6 additions & 6 deletions src/test/resources/import.sql
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
insert into form_type values('01', 'ก.01');
insert into form_type values('04', 'ก.04');
insert into form_type values('05', 'ก.05');
insert into form_type values('06', 'ก.06');
insert into form_type values('07', 'ก.07');
insert into form_type values('08', 'ก.08');
insert into form_type (id, form_desc) values('01', 'ก.01');
insert into form_type (id, form_desc) values('04', 'ก.04');
insert into form_type (id, form_desc) values('05', 'ก.05');
insert into form_type (id, form_desc) values('06', 'ก.06');
insert into form_type (id, form_desc) values('07', 'ก.07');
insert into form_type (id, form_desc) values('08', 'ก.08');

insert into document (id, description, category, type, FLAG_HAS_SUB, FORM_TYPE_ID) values(DOCUMENT_SEQ.nextVal, 'descriptiontest', 'ก.01', 'ต้นฉบับ', 'false', '01');
insert into document (id, description, category, type, FLAG_HAS_SUB, FORM_TYPE_ID) values(DOCUMENT_SEQ.nextVal, 'description', 'ก.01', 'ต้นฉบับ', 'true', '01');
Expand Down