├── .circleci └── config.yml ├── .gitignore ├── pom.xml ├── readme.md ├── renovate.json ├── sample-app-kickstart ├── pom.xml └── src │ ├── main │ ├── java │ │ └── pl │ │ │ └── piomin │ │ │ └── samples │ │ │ └── spring │ │ │ └── graphql │ │ │ ├── SampleSpringBootGraphQLKickstartApp.java │ │ │ ├── domain │ │ │ ├── Department.java │ │ │ ├── DepartmentInput.java │ │ │ ├── Employee.java │ │ │ ├── EmployeeInput.java │ │ │ ├── Organization.java │ │ │ └── OrganizationInput.java │ │ │ ├── filter │ │ │ ├── EmployeeFilter.java │ │ │ └── FilterField.java │ │ │ ├── repository │ │ │ ├── DepartmentRepository.java │ │ │ ├── EmployeeRepository.java │ │ │ └── OrganizationRepository.java │ │ │ └── resolver │ │ │ ├── DepartmentMutableResolver.java │ │ │ ├── DepartmentQueryResolver.java │ │ │ ├── EmployeeMutableResolver.java │ │ │ ├── EmployeeQueryResolver.java │ │ │ ├── OrganizationMutableResolver.java │ │ │ └── OrganizationQueryResolver.java │ └── resources │ │ ├── application.yml │ │ ├── data.sql │ │ └── graphql │ │ ├── department.graphqls │ │ ├── employee.graphqls │ │ └── organization.graphqls │ └── test │ ├── java │ └── pl │ │ └── piomin │ │ └── samples │ │ └── spring │ │ └── graphql │ │ ├── DepartmentMutableResolverTests.java │ │ ├── DepartmentQueryResolverTests.java │ │ ├── EmployeeMutableResolverTests.java │ │ ├── EmployeeQueryResolverTests.java │ │ ├── OrganizationMutableResolverTests.java │ │ └── OrganizationQueryResolverTests.java │ └── resources │ ├── departmentById.graphql │ ├── departments.graphql │ ├── employeeById.graphql │ ├── employees.graphql │ ├── employeesWithFilter.graphql │ ├── newDepartment.graphql │ ├── newEmployee.graphql │ ├── newOrganization.graphql │ ├── organizationById.graphql │ └── organizations.graphql ├── sample-app-netflix-dgs ├── pom.xml └── src │ ├── main │ ├── java │ │ └── pl │ │ │ └── piomin │ │ │ └── samples │ │ │ └── spring │ │ │ └── graphql │ │ │ ├── SampleSpringBootGraphQLApp.java │ │ │ ├── context │ │ │ ├── EmployeeContext.java │ │ │ └── EmployeeContextBuilder.java │ │ │ ├── domain │ │ │ ├── Department.java │ │ │ ├── DepartmentInput.java │ │ │ ├── Employee.java │ │ │ ├── EmployeeInput.java │ │ │ ├── Organization.java │ │ │ └── OrganizationInput.java │ │ │ ├── fetcher │ │ │ ├── DepartmentFetcher.java │ │ │ ├── DepartmentMutation.java │ │ │ ├── EmployeeFetcher.java │ │ │ ├── EmployeeMutation.java │ │ │ ├── OrganizationFetcher.java │ │ │ └── OrganizationMutation.java │ │ │ ├── filter │ │ │ ├── EmployeeFilter.java │ │ │ └── FilterField.java │ │ │ └── repository │ │ │ ├── DepartmentRepository.java │ │ │ ├── EmployeeRepository.java │ │ │ └── OrganizationRepository.java │ └── resources │ │ ├── application.yml │ │ ├── data.sql │ │ └── schema │ │ ├── department.graphqls │ │ ├── employee.graphqls │ │ └── organization.graphqls │ └── test │ └── java │ └── pl │ └── piomin │ └── samples │ └── spring │ └── graphql │ ├── DepartmentFetcherTests.java │ ├── DepartmentMutationTests.java │ ├── EmployeeFetcherTests.java │ ├── EmployeeMutationTests.java │ ├── OrganizationFetcherTests.java │ └── OrganizationMutationTests.java └── sample-app-spring-graphql ├── pom.xml └── src ├── main ├── java │ └── pl │ │ └── piomin │ │ └── sample │ │ └── spring │ │ └── graphql │ │ ├── SampleSpringBootGraphQL.java │ │ ├── domain │ │ ├── Department.java │ │ ├── DepartmentInput.java │ │ ├── Employee.java │ │ ├── EmployeeInput.java │ │ ├── Organization.java │ │ └── OrganizationInput.java │ │ ├── filter │ │ ├── EmployeeFilter.java │ │ └── FilterField.java │ │ ├── repository │ │ ├── DepartmentRepository.java │ │ ├── EmployeeRepository.java │ │ └── OrganizationRepository.java │ │ └── resolver │ │ ├── DepartmentController.java │ │ ├── EmployeeController.java │ │ └── OrganizationController.java └── resources │ ├── application.yml │ ├── graphql │ ├── department.graphqls │ ├── employee.graphqls │ └── organization.graphqls │ └── import.sql └── test └── java └── pl └── piomin └── sample └── spring └── graphql ├── DepartmentControllerTests.java ├── EmployeeControllerTests.java └── OrganizationControllerTests.java /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | analyze: 5 | docker: 6 | - image: 'cimg/openjdk:11.0' 7 | steps: 8 | - checkout 9 | - run: 10 | name: Analyze on SonarCloud 11 | command: mvn verify sonar:sonar 12 | 13 | executors: 14 | j11: 15 | docker: 16 | - image: 'cimg/openjdk:11.0' 17 | 18 | orbs: 19 | maven: circleci/maven@1.4.1 20 | 21 | workflows: 22 | maven_test: 23 | jobs: 24 | - maven/test: 25 | executor: j11 26 | - analyze: 27 | context: SonarCloud -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Project exclude paths 2 | /target/ -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.4.2 10 | 11 | 12 | pl.piomin.samples 13 | sample-spring-boot-graphql 14 | 1.0 15 | pom 16 | 17 | 18 | sample-app-netflix-dgs 19 | sample-app-kickstart 20 | sample-app-spring-graphql 21 | 22 | 23 | 24 | 11 25 | piomin_sample-spring-boot-graphql 26 | piomin 27 | https://sonarcloud.io 28 | 29 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Spring Boot GraphQL Demo Project [![Twitter](https://img.shields.io/twitter/follow/piotr_minkowski.svg?style=social&logo=twitter&label=Follow%20Me)](https://twitter.com/piotr_minkowski) 2 | 3 | [![CircleCI](https://circleci.com/gh/piomin/sample-spring-boot-graphql.svg?style=svg)](https://circleci.com/gh/piomin/sample-spring-boot-graphql) 4 | 5 | [![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-black.svg)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-boot-graphql) 6 | [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-spring-boot-graphql&metric=bugs)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-boot-graphql) 7 | [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-spring-boot-graphql&metric=coverage)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-boot-graphql) 8 | [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=piomin_sample-spring-boot-graphql&metric=ncloc)](https://sonarcloud.io/dashboard?id=piomin_sample-spring-boot-graphql) 9 | 10 | In this demo repository I'm demonstrating the most interesting libraries and features for integrating Spring Boot with GraphQL. 11 | 12 | ## Important notes 13 | This repository has been reorganized and now contains two different applications. First of them, `sample-app-kickstart` shows how to use the project called [GraphQL Kickstart](https://github.com/graphql-java-kickstart/graphql-spring-boot). The second of them `sample-app-netflix-dgs` shows how to use [Netflix DGS](https://netflix.github.io/dgs) library for GraphQL with Spring Boot. 14 | 15 | ## Getting Started 16 | 1. How to simplify Spring Boot and GraphQL development with GraphQL Kickstart library. The article describes more advanced solution like filtering or joins with a database. The example is available in the branch [master](https://github.com/piomin/sample-spring-boot-graphql/tree/master/sample-app-kickstart). A detailed guide may be found in the following article: [An Advanced Guide to GraphQL with Spring Boot](https://piotrminkowski.com/2020/07/31/an-advanced-guide-to-graphql-with-spring-boot/) 17 | 2. How to simplify Spring Boot and GraphQL development with Netflix DGS library. The example is available in the branch [master](https://github.com/piomin/sample-spring-boot-graphql/tree/master/sample-app-netflix-dgs). A detailed guide may be found in the following article: [An Advanced GraphQL with Spring Boot and Netflix DGS](https://piotrminkowski.com/2021/04/08/an-advanced-graphql-with-spring-boot-and-netflix-dgs/). 18 | 3. How to simplify Spring Boot and GraphQL development with Spring for Graph library. The example is available in the branch [master](https://github.com/piomin/sample-spring-boot-graphql/tree/master/sample-app-spring-graphql). A detailed guide may be found in the following article: [An Advanced GraphQL with Spring Boot](https://piotrminkowski.com/2023/01/18/an-advanced-graphql-with-spring-boot/). 19 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base",":dependencyDashboard" 5 | ], 6 | "packageRules": [ 7 | { 8 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 9 | "automerge": true 10 | } 11 | ], 12 | "prCreation": "not-pending" 13 | } -------------------------------------------------------------------------------- /sample-app-kickstart/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | pl.piomin.samples 8 | sample-spring-boot-graphql 9 | 1.0 10 | 11 | sample-app-kickstart 12 | 1.0 13 | 14 | 15 | 7.2.0 16 | ${artifactId} 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-data-jpa 27 | 28 | 29 | com.h2database 30 | h2 31 | runtime 32 | 33 | 34 | org.projectlombok 35 | lombok 36 | 37 | 38 | com.graphql-java-kickstart 39 | graphql-spring-boot-starter 40 | ${graphql.spring.version} 41 | 42 | 43 | com.graphql-java-kickstart 44 | graphiql-spring-boot-starter 45 | ${graphql.spring.version} 46 | 47 | 48 | com.graphql-java-kickstart 49 | graphql-spring-boot-starter-test 50 | ${graphql.spring.version} 51 | test 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-starter-test 56 | test 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-maven-plugin 65 | 66 | 67 | org.jacoco 68 | jacoco-maven-plugin 69 | 0.8.11 70 | 71 | 72 | 73 | prepare-agent 74 | 75 | 76 | 77 | report 78 | test 79 | 80 | report 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/SampleSpringBootGraphQLKickstartApp.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SampleSpringBootGraphQLKickstartApp { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SampleSpringBootGraphQLKickstartApp.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/domain/Department.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | import java.util.Set; 10 | 11 | @Entity 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 16 | public class Department { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | @EqualsAndHashCode.Include 20 | private Integer id; 21 | private String name; 22 | @OneToMany(mappedBy = "department") 23 | private Set employees; 24 | @ManyToOne(fetch = FetchType.LAZY) 25 | private Organization organization; 26 | } 27 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/domain/DepartmentInput.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class DepartmentInput { 11 | private String name; 12 | private Integer organizationId; 13 | } 14 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/domain/Employee.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | 10 | @Entity 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 15 | public class Employee { 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | @EqualsAndHashCode.Include 19 | private Integer id; 20 | private String firstName; 21 | private String lastName; 22 | private String position; 23 | private int salary; 24 | private int age; 25 | @ManyToOne(fetch = FetchType.LAZY) 26 | private Department department; 27 | @ManyToOne(fetch = FetchType.LAZY) 28 | private Organization organization; 29 | } 30 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/domain/EmployeeInput.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class EmployeeInput { 11 | private String firstName; 12 | private String lastName; 13 | private String position; 14 | private int salary; 15 | private int age; 16 | private Integer departmentId; 17 | private Integer organizationId; 18 | } 19 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/domain/Organization.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | import java.util.Set; 10 | 11 | @Entity 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 16 | public class Organization { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | @EqualsAndHashCode.Include 20 | private Integer id; 21 | private String name; 22 | @OneToMany(mappedBy = "organization") 23 | private Set departments; 24 | @OneToMany(mappedBy = "organization") 25 | private Set employees; 26 | } 27 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/domain/OrganizationInput.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class OrganizationInput { 11 | private String name; 12 | } 13 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/filter/EmployeeFilter.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.filter; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class EmployeeFilter { 7 | private FilterField salary; 8 | private FilterField age; 9 | private FilterField position; 10 | } 11 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/filter/FilterField.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.filter; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.criteria.CriteriaBuilder; 6 | import javax.persistence.criteria.Path; 7 | import javax.persistence.criteria.Predicate; 8 | 9 | @Data 10 | public class FilterField { 11 | private String operator; 12 | private String value; 13 | 14 | public Predicate generateCriteria(CriteriaBuilder builder, Path field) { 15 | try { 16 | int v = Integer.parseInt(value); 17 | switch (operator) { 18 | case "lt": return builder.lt(field, v); 19 | case "le": return builder.le(field, v); 20 | case "gt": return builder.gt(field, v); 21 | case "ge": return builder.ge(field, v); 22 | case "eq": return builder.equal(field, v); 23 | } 24 | } catch (NumberFormatException e) { 25 | switch (operator) { 26 | case "endsWith": return builder.like(field, "%" + value); 27 | case "startsWith": return builder.like(field, value + "%"); 28 | case "contains": return builder.like(field, "%" + value + "%"); 29 | case "eq": return builder.equal(field, value); 30 | } 31 | } 32 | 33 | return null; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/repository/DepartmentRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 4 | import org.springframework.data.repository.CrudRepository; 5 | import pl.piomin.samples.spring.graphql.domain.Department; 6 | 7 | public interface DepartmentRepository extends CrudRepository, 8 | JpaSpecificationExecutor { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/repository/EmployeeRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 4 | import org.springframework.data.repository.CrudRepository; 5 | import pl.piomin.samples.spring.graphql.domain.Employee; 6 | 7 | public interface EmployeeRepository extends CrudRepository, 8 | JpaSpecificationExecutor { 9 | } 10 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/repository/OrganizationRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import pl.piomin.samples.spring.graphql.domain.Organization; 5 | 6 | public interface OrganizationRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/resolver/DepartmentMutableResolver.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.resolver; 2 | 3 | import graphql.kickstart.tools.GraphQLMutationResolver; 4 | import org.springframework.stereotype.Component; 5 | import pl.piomin.samples.spring.graphql.domain.Department; 6 | import pl.piomin.samples.spring.graphql.domain.DepartmentInput; 7 | import pl.piomin.samples.spring.graphql.domain.Organization; 8 | import pl.piomin.samples.spring.graphql.repository.DepartmentRepository; 9 | import pl.piomin.samples.spring.graphql.repository.OrganizationRepository; 10 | 11 | @Component 12 | public class DepartmentMutableResolver implements GraphQLMutationResolver { 13 | 14 | DepartmentRepository departmentRepository; 15 | OrganizationRepository organizationRepository; 16 | 17 | DepartmentMutableResolver(DepartmentRepository departmentRepository, OrganizationRepository organizationRepository) { 18 | this.departmentRepository = departmentRepository; 19 | this.organizationRepository = organizationRepository; 20 | } 21 | 22 | public Department newDepartment(DepartmentInput departmentInput) { 23 | Organization organization = organizationRepository.findById(departmentInput.getOrganizationId()).get(); 24 | return departmentRepository.save(new Department(null, departmentInput.getName(), null, organization)); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/resolver/DepartmentQueryResolver.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.resolver; 2 | 3 | import graphql.kickstart.tools.GraphQLQueryResolver; 4 | import graphql.schema.DataFetchingEnvironment; 5 | import graphql.schema.DataFetchingFieldSelectionSet; 6 | import org.springframework.data.jpa.domain.Specification; 7 | import org.springframework.stereotype.Component; 8 | import pl.piomin.samples.spring.graphql.domain.Department; 9 | import pl.piomin.samples.spring.graphql.domain.Employee; 10 | import pl.piomin.samples.spring.graphql.domain.Organization; 11 | import pl.piomin.samples.spring.graphql.repository.DepartmentRepository; 12 | 13 | import javax.persistence.criteria.Fetch; 14 | import javax.persistence.criteria.Join; 15 | import javax.persistence.criteria.JoinType; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.NoSuchElementException; 19 | 20 | @Component 21 | public class DepartmentQueryResolver implements GraphQLQueryResolver { 22 | 23 | private DepartmentRepository repository; 24 | 25 | DepartmentQueryResolver(DepartmentRepository repository) { 26 | this.repository = repository; 27 | } 28 | 29 | public Iterable departments(DataFetchingEnvironment environment) { 30 | DataFetchingFieldSelectionSet s = environment.getSelectionSet(); 31 | List> specifications = new ArrayList<>(); 32 | if (s.contains("employees") && !s.contains("organization")) 33 | return repository.findAll(fetchEmployees()); 34 | else if (!s.contains("employees") && s.contains("organization")) 35 | return repository.findAll(fetchOrganization()); 36 | else if (s.contains("employees") && s.contains("organization")) 37 | return repository.findAll(fetchEmployees().and(fetchOrganization())); 38 | else 39 | return repository.findAll(); 40 | } 41 | 42 | public Department department(Integer id, DataFetchingEnvironment environment) { 43 | Specification spec = byId(id); 44 | DataFetchingFieldSelectionSet selectionSet = environment.getSelectionSet(); 45 | if (selectionSet.contains("employees")) 46 | spec = spec.and(fetchEmployees()); 47 | if (selectionSet.contains("organization")) 48 | spec = spec.and(fetchOrganization()); 49 | return repository.findOne(spec).orElseThrow(NoSuchElementException::new); 50 | } 51 | 52 | private Specification fetchOrganization() { 53 | return (Specification) (root, query, builder) -> { 54 | Fetch f = root.fetch("organization", JoinType.LEFT); 55 | Join join = (Join) f; 56 | return join.getOn(); 57 | }; 58 | } 59 | 60 | private Specification fetchEmployees() { 61 | return (Specification) (root, query, builder) -> { 62 | Fetch f = root.fetch("employees", JoinType.LEFT); 63 | Join join = (Join) f; 64 | return join.getOn(); 65 | }; 66 | } 67 | 68 | private Specification byId(Integer id) { 69 | return (Specification) (root, query, builder) -> builder.equal(root.get("id"), id); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/resolver/EmployeeMutableResolver.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.resolver; 2 | 3 | import graphql.kickstart.tools.GraphQLMutationResolver; 4 | import org.springframework.stereotype.Component; 5 | import pl.piomin.samples.spring.graphql.domain.Department; 6 | import pl.piomin.samples.spring.graphql.domain.Employee; 7 | import pl.piomin.samples.spring.graphql.domain.EmployeeInput; 8 | import pl.piomin.samples.spring.graphql.domain.Organization; 9 | import pl.piomin.samples.spring.graphql.repository.DepartmentRepository; 10 | import pl.piomin.samples.spring.graphql.repository.EmployeeRepository; 11 | import pl.piomin.samples.spring.graphql.repository.OrganizationRepository; 12 | 13 | @Component 14 | public class EmployeeMutableResolver implements GraphQLMutationResolver { 15 | 16 | DepartmentRepository departmentRepository; 17 | EmployeeRepository employeeRepository; 18 | OrganizationRepository organizationRepository; 19 | 20 | EmployeeMutableResolver(DepartmentRepository departmentRepository, EmployeeRepository employeeRepository, OrganizationRepository organizationRepository) { 21 | this.departmentRepository = departmentRepository; 22 | this.employeeRepository = employeeRepository; 23 | this.organizationRepository = organizationRepository; 24 | } 25 | 26 | public Employee newEmployee(EmployeeInput employeeInput) { 27 | Department department = departmentRepository.findById(employeeInput.getDepartmentId()).get(); 28 | Organization organization = organizationRepository.findById(employeeInput.getOrganizationId()).get(); 29 | return employeeRepository.save(new Employee(null, employeeInput.getFirstName(), employeeInput.getLastName(), 30 | employeeInput.getPosition(), employeeInput.getAge(), employeeInput.getSalary(), 31 | department, organization)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/resolver/EmployeeQueryResolver.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.resolver; 2 | 3 | import graphql.kickstart.tools.GraphQLQueryResolver; 4 | import org.springframework.data.jpa.domain.Specification; 5 | import org.springframework.stereotype.Component; 6 | import pl.piomin.samples.spring.graphql.domain.Employee; 7 | import pl.piomin.samples.spring.graphql.filter.EmployeeFilter; 8 | import pl.piomin.samples.spring.graphql.filter.FilterField; 9 | import pl.piomin.samples.spring.graphql.repository.EmployeeRepository; 10 | 11 | @Component 12 | public class EmployeeQueryResolver implements GraphQLQueryResolver { 13 | 14 | private EmployeeRepository repository; 15 | 16 | EmployeeQueryResolver(EmployeeRepository repository) { 17 | this.repository = repository; 18 | } 19 | 20 | public Iterable employees() { 21 | return repository.findAll(); 22 | } 23 | 24 | public Employee employee(Integer id) { 25 | return repository.findById(id).get(); 26 | } 27 | 28 | public Iterable employeesWithFilter(EmployeeFilter filter) { 29 | Specification spec = null; 30 | if (filter.getSalary() != null) 31 | spec = bySalary(filter.getSalary()); 32 | if (filter.getAge() != null) 33 | spec = (spec == null ? byAge(filter.getAge()) : spec.and(byAge(filter.getAge()))); 34 | if (filter.getPosition() != null) 35 | spec = (spec == null ? byPosition(filter.getPosition()) : 36 | spec.and(byPosition(filter.getPosition()))); 37 | if (spec != null) 38 | return repository.findAll(spec); 39 | else 40 | return repository.findAll(); 41 | } 42 | 43 | private Specification bySalary(FilterField filterField) { 44 | return (Specification) (root, query, builder) -> filterField.generateCriteria(builder, root.get("salary")); 45 | } 46 | 47 | private Specification byAge(FilterField filterField) { 48 | return (Specification) (root, query, builder) -> filterField.generateCriteria(builder, root.get("age")); 49 | } 50 | 51 | private Specification byPosition(FilterField filterField) { 52 | return (Specification) (root, query, builder) -> filterField.generateCriteria(builder, root.get("position")); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/resolver/OrganizationMutableResolver.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.resolver; 2 | 3 | import graphql.kickstart.tools.GraphQLMutationResolver; 4 | import org.springframework.stereotype.Component; 5 | import pl.piomin.samples.spring.graphql.domain.Organization; 6 | import pl.piomin.samples.spring.graphql.domain.OrganizationInput; 7 | import pl.piomin.samples.spring.graphql.repository.OrganizationRepository; 8 | 9 | @Component 10 | public class OrganizationMutableResolver implements GraphQLMutationResolver { 11 | 12 | OrganizationRepository repository; 13 | 14 | OrganizationMutableResolver(OrganizationRepository repository) { 15 | this.repository = repository; 16 | } 17 | 18 | public Organization newOrganization(OrganizationInput organizationInput) { 19 | return repository.save(new Organization(null, organizationInput.getName(), null, null)); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/java/pl/piomin/samples/spring/graphql/resolver/OrganizationQueryResolver.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.resolver; 2 | 3 | import graphql.kickstart.tools.GraphQLQueryResolver; 4 | import org.springframework.stereotype.Component; 5 | import pl.piomin.samples.spring.graphql.domain.Organization; 6 | import pl.piomin.samples.spring.graphql.repository.OrganizationRepository; 7 | 8 | @Component 9 | public class OrganizationQueryResolver implements GraphQLQueryResolver { 10 | 11 | private OrganizationRepository repository; 12 | 13 | OrganizationQueryResolver(OrganizationRepository repository) { 14 | this.repository = repository; 15 | } 16 | 17 | public Iterable organizations() { 18 | return repository.findAll(); 19 | } 20 | 21 | public Organization organization(Integer id) { 22 | return repository.findById(id).orElseThrow(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: sample-spring-boot-graphql 4 | datasource: 5 | url: jdbc:h2:mem:testdb 6 | driverClassName: org.h2.Driver 7 | username: sa 8 | password: password 9 | hikari: 10 | connection-timeout: 2000 11 | initialization-fail-timeout: 0 12 | jpa: 13 | database-platform: org.hibernate.dialect.H2Dialect 14 | properties: 15 | hibernate: 16 | show_sql: true 17 | format_sql: true 18 | enable_lazy_load_no_trans: true -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | insert into organization (name) values ('Test1'); 2 | insert into organization (name) values ('Test2'); 3 | insert into organization (name) values ('Test3'); 4 | insert into organization (name) values ('Test4'); 5 | insert into organization (name) values ('Test5'); 6 | insert into department (name, organization_id) values ('Test1', 1); 7 | insert into department (name, organization_id) values ('Test2', 1); 8 | insert into department (name, organization_id) values ('Test3', 1); 9 | insert into department (name, organization_id) values ('Test4', 2); 10 | insert into department (name, organization_id) values ('Test5', 2); 11 | insert into department (name, organization_id) values ('Test6', 3); 12 | insert into department (name, organization_id) values ('Test7', 4); 13 | insert into department (name, organization_id) values ('Test8', 5); 14 | insert into department (name, organization_id) values ('Test9', 5); 15 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('John', 'Smith', 'Developer', 10000, 30, 1, 1); 16 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Adam', 'Hamilton', 'Developer', 12000, 35, 1, 1); 17 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Tracy', 'Smith', 'Architect', 15000, 40, 1, 1); 18 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Lucy', 'Kim', 'Developer', 13000, 25, 2, 1); 19 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Peter', 'Wright', 'Director', 50000, 50, 4, 2); 20 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Alan', 'Murray', 'Developer', 20000, 37, 4, 2); 21 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Pamela', 'Anderson', 'Analyst', 7000, 27, 4, 2); 22 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/resources/graphql/department.graphqls: -------------------------------------------------------------------------------- 1 | type QueryResolver { 2 | departments: [Department] 3 | department(id: ID!): Department! 4 | } 5 | 6 | type MutationResolver { 7 | newDepartment(department: DepartmentInput!): Department 8 | } 9 | 10 | input DepartmentInput { 11 | name: String! 12 | organizationId: Int 13 | } 14 | 15 | type Department { 16 | id: ID! 17 | name: String! 18 | organization: Organization 19 | employees: [Employee] 20 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/resources/graphql/employee.graphqls: -------------------------------------------------------------------------------- 1 | extend type QueryResolver { 2 | employees: [Employee] 3 | employeesWithFilter(filter: EmployeeFilter): [Employee] 4 | employee(id: ID!): Employee! 5 | } 6 | 7 | extend type MutationResolver { 8 | newEmployee(employee: EmployeeInput!): Employee 9 | } 10 | 11 | input EmployeeInput { 12 | firstName: String! 13 | lastName: String! 14 | position: String! 15 | salary: Int 16 | age: Int 17 | organizationId: Int! 18 | departmentId: Int! 19 | } 20 | 21 | type Employee { 22 | id: ID! 23 | firstName: String! 24 | lastName: String! 25 | position: String! 26 | salary: Int 27 | age: Int 28 | department: Department 29 | organization: Organization 30 | } 31 | 32 | input EmployeeFilter { 33 | salary: FilterField 34 | age: FilterField 35 | position: FilterField 36 | } 37 | 38 | input FilterField { 39 | operator: String! 40 | value: String! 41 | } 42 | 43 | schema { 44 | query: QueryResolver 45 | mutation: MutationResolver 46 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/main/resources/graphql/organization.graphqls: -------------------------------------------------------------------------------- 1 | extend type QueryResolver { 2 | organizations: [Organization] 3 | organization(id: ID!): Organization! 4 | } 5 | 6 | extend type MutationResolver { 7 | newOrganization(organization: OrganizationInput!): Organization 8 | } 9 | 10 | input OrganizationInput { 11 | name: String! 12 | } 13 | 14 | type Organization { 15 | id: ID! 16 | name: String! 17 | employees: [Employee] 18 | departments: [Department] 19 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/java/pl/piomin/samples/spring/graphql/DepartmentMutableResolverTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.graphql.spring.boot.test.GraphQLTestTemplate; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.spring.graphql.domain.Department; 9 | import pl.piomin.samples.spring.graphql.domain.Employee; 10 | 11 | import java.io.IOException; 12 | 13 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 14 | public class DepartmentMutableResolverTests { 15 | 16 | @Autowired 17 | GraphQLTestTemplate template; 18 | 19 | @Test 20 | void newDepartment() throws IOException { 21 | Department department = template.postForResource("newDepartment.graphql") 22 | .get("$.data.newDepartment", Department.class); 23 | Assertions.assertNotNull(department); 24 | Assertions.assertNotNull(department.getId()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/java/pl/piomin/samples/spring/graphql/DepartmentQueryResolverTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.graphql.spring.boot.test.GraphQLTestTemplate; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.spring.graphql.domain.Department; 9 | import pl.piomin.samples.spring.graphql.domain.Employee; 10 | 11 | import java.io.IOException; 12 | 13 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 14 | public class DepartmentQueryResolverTests { 15 | 16 | @Autowired 17 | GraphQLTestTemplate template; 18 | 19 | @Test 20 | void departments() throws IOException { 21 | Department[] departments = template.postForResource("departments.graphql") 22 | .get("$.data.departments", Department[].class); 23 | Assertions.assertTrue(departments.length > 0); 24 | } 25 | 26 | @Test 27 | void departmentById() throws IOException { 28 | Department department = template.postForResource("departmentById.graphql") 29 | .get("$.data.department", Department.class); 30 | Assertions.assertNotNull(department); 31 | Assertions.assertNotNull(department.getId()); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/java/pl/piomin/samples/spring/graphql/EmployeeMutableResolverTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.graphql.spring.boot.test.GraphQLTest; 4 | import com.graphql.spring.boot.test.GraphQLTestTemplate; 5 | import org.junit.jupiter.api.Assertions; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import pl.piomin.samples.spring.graphql.domain.Employee; 10 | 11 | import java.io.IOException; 12 | 13 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 14 | public class EmployeeMutableResolverTests { 15 | 16 | @Autowired 17 | GraphQLTestTemplate template; 18 | 19 | @Test 20 | void newEmployee() throws IOException { 21 | Employee employee = template.postForResource("newEmployee.graphql") 22 | .get("$.data.newEmployee", Employee.class); 23 | Assertions.assertNotNull(employee); 24 | Assertions.assertNotNull(employee.getId()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/java/pl/piomin/samples/spring/graphql/EmployeeQueryResolverTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.graphql.spring.boot.test.GraphQLTestTemplate; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.spring.graphql.domain.Employee; 9 | 10 | import java.io.IOException; 11 | 12 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 13 | public class EmployeeQueryResolverTests { 14 | 15 | @Autowired 16 | GraphQLTestTemplate template; 17 | 18 | @Test 19 | void employees() throws IOException { 20 | Employee[] employees = template.postForResource("employees.graphql") 21 | .get("$.data.employees", Employee[].class); 22 | Assertions.assertTrue(employees.length > 0); 23 | } 24 | 25 | @Test 26 | void employeeById() throws IOException { 27 | Employee employee = template.postForResource("employeeById.graphql") 28 | .get("$.data.employee", Employee.class); 29 | Assertions.assertNotNull(employee); 30 | Assertions.assertNotNull(employee.getId()); 31 | } 32 | 33 | @Test 34 | void employeesWithFilter() throws IOException { 35 | Employee[] employees = template.postForResource("employeesWithFilter.graphql") 36 | .get("$.data.employeesWithFilter", Employee[].class); 37 | Assertions.assertEquals(4, employees.length); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/java/pl/piomin/samples/spring/graphql/OrganizationMutableResolverTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.graphql.spring.boot.test.GraphQLTestTemplate; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.spring.graphql.domain.Organization; 9 | 10 | import java.io.IOException; 11 | 12 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 13 | public class OrganizationMutableResolverTests { 14 | 15 | @Autowired 16 | GraphQLTestTemplate template; 17 | 18 | @Test 19 | void newOrganization() throws IOException { 20 | Organization organization = template.postForResource("newOrganization.graphql") 21 | .get("$.data.newOrganization", Organization.class); 22 | Assertions.assertNotNull(organization); 23 | Assertions.assertNotNull(organization.getId()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/java/pl/piomin/samples/spring/graphql/OrganizationQueryResolverTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.graphql.spring.boot.test.GraphQLTestTemplate; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.spring.graphql.domain.Organization; 9 | 10 | import java.io.IOException; 11 | 12 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 13 | public class OrganizationQueryResolverTests { 14 | 15 | @Autowired 16 | GraphQLTestTemplate template; 17 | 18 | @Test 19 | void organizations() throws IOException { 20 | Organization[] organizations = template.postForResource("organizations.graphql") 21 | .get("$.data.organizations", Organization[].class); 22 | Assertions.assertTrue(organizations.length > 0); 23 | } 24 | 25 | @Test 26 | void organizationById() throws IOException { 27 | Organization organization = template.postForResource("organizationById.graphql") 28 | .get("$.data.organization", Organization.class); 29 | Assertions.assertNotNull(organization); 30 | Assertions.assertNotNull(organization.getId()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/resources/departmentById.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | department(id: 1) { 3 | id 4 | name 5 | organization { 6 | id 7 | } 8 | employees { 9 | id 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/resources/departments.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | departments { 3 | id 4 | name 5 | } 6 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/resources/employeeById.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | employee(id: 1) { 3 | id 4 | firstName 5 | lastName 6 | salary 7 | } 8 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/resources/employees.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | employees { 3 | id 4 | firstName 5 | lastName 6 | salary 7 | } 8 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/resources/employeesWithFilter.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | employeesWithFilter(filter: { 3 | salary: { 4 | operator: "gt" 5 | value: "12000" 6 | } 7 | }) { 8 | id 9 | firstName 10 | lastName 11 | salary 12 | } 13 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/resources/newDepartment.graphql: -------------------------------------------------------------------------------- 1 | mutation { 2 | newDepartment(department: { 3 | name: "Test10" 4 | organizationId: 2 5 | }) { 6 | id 7 | } 8 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/resources/newEmployee.graphql: -------------------------------------------------------------------------------- 1 | mutation { 2 | newEmployee(employee: { 3 | firstName: "John" 4 | lastName: "Wick" 5 | position: "developer" 6 | salary: 10000 7 | age: 20 8 | departmentId: 1 9 | organizationId: 1 10 | }) { 11 | id 12 | } 13 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/resources/newOrganization.graphql: -------------------------------------------------------------------------------- 1 | mutation { 2 | newOrganization(organization: { 3 | name: "Test5" 4 | }) { 5 | id 6 | } 7 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/resources/organizationById.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | organization(id: 1) { 3 | id 4 | name 5 | } 6 | } -------------------------------------------------------------------------------- /sample-app-kickstart/src/test/resources/organizations.graphql: -------------------------------------------------------------------------------- 1 | query { 2 | organizations { 3 | id 4 | name 5 | } 6 | } -------------------------------------------------------------------------------- /sample-app-netflix-dgs/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | pl.piomin.samples 10 | sample-spring-boot-graphql 11 | 1.0 12 | 13 | sample-app-netflix-dgs 14 | 1.0 15 | 16 | 17 | 3.12.1 18 | ${artifactId} 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-data-jpa 29 | 30 | 31 | com.h2database 32 | h2 33 | runtime 34 | 35 | 36 | org.projectlombok 37 | lombok 38 | 39 | 40 | com.netflix.graphql.dgs 41 | graphql-dgs-spring-boot-starter 42 | ${netflix-dgs.spring.version} 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-test 47 | test 48 | 49 | 50 | 51 | 52 | 53 | 54 | org.springframework.boot 55 | spring-boot-maven-plugin 56 | 57 | 58 | org.jacoco 59 | jacoco-maven-plugin 60 | 0.8.11 61 | 62 | 63 | 64 | prepare-agent 65 | 66 | 67 | 68 | report 69 | test 70 | 71 | report 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/SampleSpringBootGraphQLApp.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SampleSpringBootGraphQLApp { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SampleSpringBootGraphQLApp.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/context/EmployeeContext.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.context; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import pl.piomin.samples.spring.graphql.domain.Employee; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Data 11 | @AllArgsConstructor 12 | public class EmployeeContext { 13 | private List employees = new ArrayList<>(); 14 | } 15 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/context/EmployeeContextBuilder.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.context; 2 | 3 | import com.netflix.graphql.dgs.context.DgsCustomContextBuilder; 4 | import org.springframework.stereotype.Component; 5 | import pl.piomin.samples.spring.graphql.domain.Employee; 6 | 7 | import java.util.List; 8 | 9 | @Component 10 | public class EmployeeContextBuilder implements DgsCustomContextBuilder { 11 | 12 | private List employees; 13 | 14 | public EmployeeContextBuilder withEmployees(List employees) { 15 | this.employees = employees; 16 | return this; 17 | } 18 | 19 | @Override 20 | public EmployeeContext build() { 21 | return new EmployeeContext(employees); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/domain/Department.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | import java.util.Set; 10 | 11 | @Entity 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 16 | public class Department { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | @EqualsAndHashCode.Include 20 | private Integer id; 21 | private String name; 22 | @OneToMany(mappedBy = "department") 23 | private Set employees; 24 | @ManyToOne(fetch = FetchType.LAZY) 25 | private Organization organization; 26 | } 27 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/domain/DepartmentInput.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class DepartmentInput { 11 | private String name; 12 | private Integer organizationId; 13 | } 14 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/domain/Employee.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | 10 | @Entity 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 15 | public class Employee { 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | @EqualsAndHashCode.Include 19 | private Integer id; 20 | private String firstName; 21 | private String lastName; 22 | private String position; 23 | private int salary; 24 | private int age; 25 | @ManyToOne(fetch = FetchType.LAZY) 26 | private Department department; 27 | @ManyToOne(fetch = FetchType.LAZY) 28 | private Organization organization; 29 | } 30 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/domain/EmployeeInput.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class EmployeeInput { 11 | private String firstName; 12 | private String lastName; 13 | private String position; 14 | private int salary; 15 | private int age; 16 | private Integer departmentId; 17 | private Integer organizationId; 18 | } 19 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/domain/Organization.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | import java.util.Set; 10 | 11 | @Entity 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 16 | public class Organization { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | @EqualsAndHashCode.Include 20 | private Integer id; 21 | private String name; 22 | @OneToMany(mappedBy = "organization") 23 | private Set departments; 24 | @OneToMany(mappedBy = "organization") 25 | private Set employees; 26 | } 27 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/domain/OrganizationInput.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class OrganizationInput { 11 | private String name; 12 | } 13 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/fetcher/DepartmentFetcher.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.fetcher; 2 | 3 | import com.netflix.graphql.dgs.DgsComponent; 4 | import com.netflix.graphql.dgs.DgsData; 5 | import com.netflix.graphql.dgs.InputArgument; 6 | import com.netflix.graphql.dgs.context.DgsContext; 7 | import com.netflix.graphql.dgs.exceptions.DgsEntityNotFoundException; 8 | import graphql.schema.DataFetchingEnvironment; 9 | import graphql.schema.DataFetchingFieldSelectionSet; 10 | import org.springframework.data.jpa.domain.Specification; 11 | import pl.piomin.samples.spring.graphql.context.EmployeeContext; 12 | import pl.piomin.samples.spring.graphql.domain.Department; 13 | import pl.piomin.samples.spring.graphql.domain.Employee; 14 | import pl.piomin.samples.spring.graphql.domain.Organization; 15 | import pl.piomin.samples.spring.graphql.repository.DepartmentRepository; 16 | 17 | import javax.persistence.criteria.Fetch; 18 | import javax.persistence.criteria.Join; 19 | import javax.persistence.criteria.JoinType; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.NoSuchElementException; 23 | import java.util.Set; 24 | import java.util.stream.Collectors; 25 | 26 | @DgsComponent 27 | public class DepartmentFetcher { 28 | 29 | private DepartmentRepository repository; 30 | 31 | DepartmentFetcher(DepartmentRepository repository) { 32 | this.repository = repository; 33 | } 34 | 35 | @DgsData(parentType = "QueryResolver", field = "departments") 36 | public Iterable findAll(DataFetchingEnvironment environment) { 37 | DataFetchingFieldSelectionSet s = environment.getSelectionSet(); 38 | if (s.contains("employees") && !s.contains("organization")) 39 | return repository.findAll(fetchEmployees()); 40 | else if (!s.contains("employees") && s.contains("organization")) 41 | return repository.findAll(fetchOrganization()); 42 | else if (s.contains("employees") && s.contains("organization")) 43 | return repository.findAll(fetchEmployees().and(fetchOrganization())); 44 | else 45 | return repository.findAll(); 46 | } 47 | 48 | @DgsData(parentType = "QueryResolver", field = "department") 49 | public Department findById(@InputArgument("id") Integer id, DataFetchingEnvironment environment) { 50 | Specification spec = byId(id); 51 | DataFetchingFieldSelectionSet selectionSet = environment.getSelectionSet(); 52 | EmployeeContext employeeContext = DgsContext.getCustomContext(environment); 53 | Set employees = null; 54 | if (selectionSet.contains("employees")) { 55 | if (employeeContext.getEmployees().size() == 0) 56 | spec = spec.and(fetchEmployees()); 57 | else 58 | employees = employeeContext.getEmployees().stream() 59 | .filter(emp -> emp.getDepartment().getId().equals(id)) 60 | .collect(Collectors.toSet()); 61 | } 62 | if (selectionSet.contains("organization")) 63 | spec = spec.and(fetchOrganization()); 64 | Department department = repository.findOne(spec).orElseThrow(DgsEntityNotFoundException::new); 65 | if (employees != null) 66 | department.setEmployees(employees); 67 | return department; 68 | } 69 | 70 | private Specification fetchOrganization() { 71 | return (root, query, builder) -> { 72 | Fetch f = root.fetch("organization", JoinType.LEFT); 73 | Join join = (Join) f; 74 | return join.getOn(); 75 | }; 76 | } 77 | 78 | private Specification fetchEmployees() { 79 | return (root, query, builder) -> { 80 | Fetch f = root.fetch("employees", JoinType.LEFT); 81 | Join join = (Join) f; 82 | return join.getOn(); 83 | }; 84 | } 85 | 86 | private Specification byId(Integer id) { 87 | return (root, query, builder) -> builder.equal(root.get("id"), id); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/fetcher/DepartmentMutation.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.fetcher; 2 | 3 | import com.netflix.graphql.dgs.DgsComponent; 4 | import com.netflix.graphql.dgs.DgsData; 5 | import com.netflix.graphql.dgs.InputArgument; 6 | import pl.piomin.samples.spring.graphql.domain.Department; 7 | import pl.piomin.samples.spring.graphql.domain.DepartmentInput; 8 | import pl.piomin.samples.spring.graphql.domain.Organization; 9 | import pl.piomin.samples.spring.graphql.repository.DepartmentRepository; 10 | import pl.piomin.samples.spring.graphql.repository.OrganizationRepository; 11 | 12 | @DgsComponent 13 | public class DepartmentMutation { 14 | 15 | DepartmentRepository departmentRepository; 16 | OrganizationRepository organizationRepository; 17 | 18 | DepartmentMutation(DepartmentRepository departmentRepository, OrganizationRepository organizationRepository) { 19 | this.departmentRepository = departmentRepository; 20 | this.organizationRepository = organizationRepository; 21 | } 22 | 23 | @DgsData(parentType = "MutationResolver", field = "newDepartment") 24 | public Department newDepartment(@InputArgument("department") DepartmentInput departmentInput) { 25 | Organization organization = organizationRepository.findById(departmentInput.getOrganizationId()).orElseThrow(); 26 | return departmentRepository.save(new Department(null, departmentInput.getName(), null, organization)); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/fetcher/EmployeeFetcher.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.fetcher; 2 | 3 | import com.netflix.graphql.dgs.DgsComponent; 4 | import com.netflix.graphql.dgs.DgsData; 5 | import com.netflix.graphql.dgs.InputArgument; 6 | import com.netflix.graphql.dgs.context.DgsContext; 7 | import com.netflix.graphql.dgs.exceptions.DgsEntityNotFoundException; 8 | import graphql.execution.DataFetcherResult; 9 | import graphql.schema.DataFetchingEnvironment; 10 | import org.springframework.data.jpa.domain.Specification; 11 | import pl.piomin.samples.spring.graphql.context.EmployeeContext; 12 | import pl.piomin.samples.spring.graphql.context.EmployeeContextBuilder; 13 | import pl.piomin.samples.spring.graphql.domain.Employee; 14 | import pl.piomin.samples.spring.graphql.filter.EmployeeFilter; 15 | import pl.piomin.samples.spring.graphql.filter.FilterField; 16 | import pl.piomin.samples.spring.graphql.repository.EmployeeRepository; 17 | 18 | import java.util.List; 19 | import java.util.Optional; 20 | 21 | @DgsComponent 22 | public class EmployeeFetcher { 23 | 24 | private EmployeeRepository repository; 25 | private EmployeeContextBuilder contextBuilder; 26 | 27 | public EmployeeFetcher(EmployeeRepository repository, EmployeeContextBuilder contextBuilder) { 28 | this.repository = repository; 29 | this.contextBuilder = contextBuilder; 30 | } 31 | 32 | @DgsData(parentType = "QueryResolver", field = "employees") 33 | public List findAll(DataFetchingEnvironment dfe) { 34 | List employees = (List) repository.findAll(); 35 | contextBuilder.withEmployees(employees).build(); 36 | return employees; 37 | } 38 | 39 | @DgsData(parentType = "QueryResolver", field = "employee") 40 | public Employee findById(@InputArgument("id") Integer id, DataFetchingEnvironment dfe) { 41 | EmployeeContext employeeContext = DgsContext.getCustomContext(dfe); 42 | List employees = employeeContext.getEmployees(); 43 | Optional employeeOpt = employees.stream().filter(employee -> employee.getId().equals(id)) 44 | .findFirst(); 45 | return employeeOpt.orElseGet(() -> repository.findById(id).orElseThrow(DgsEntityNotFoundException::new)); 46 | } 47 | 48 | @DgsData(parentType = "QueryResolver", field = "employeesWithFilter") 49 | public Iterable findWithFilter(@InputArgument("filter") EmployeeFilter filter) { 50 | Specification spec = null; 51 | if (filter.getSalary() != null) 52 | spec = bySalary(filter.getSalary()); 53 | if (filter.getAge() != null) 54 | spec = (spec == null ? byAge(filter.getAge()) : spec.and(byAge(filter.getAge()))); 55 | if (filter.getPosition() != null) 56 | spec = (spec == null ? byPosition(filter.getPosition()) : 57 | spec.and(byPosition(filter.getPosition()))); 58 | if (spec != null) 59 | return repository.findAll(spec); 60 | else 61 | return repository.findAll(); 62 | } 63 | 64 | private Specification bySalary(FilterField filterField) { 65 | return (root, query, builder) -> filterField.generateCriteria(builder, root.get("salary")); 66 | } 67 | 68 | private Specification byAge(FilterField filterField) { 69 | return (root, query, builder) -> filterField.generateCriteria(builder, root.get("age")); 70 | } 71 | 72 | private Specification byPosition(FilterField filterField) { 73 | return (root, query, builder) -> filterField.generateCriteria(builder, root.get("position")); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/fetcher/EmployeeMutation.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.fetcher; 2 | 3 | import com.netflix.graphql.dgs.DgsComponent; 4 | import com.netflix.graphql.dgs.DgsData; 5 | import com.netflix.graphql.dgs.InputArgument; 6 | import pl.piomin.samples.spring.graphql.domain.Department; 7 | import pl.piomin.samples.spring.graphql.domain.Employee; 8 | import pl.piomin.samples.spring.graphql.domain.EmployeeInput; 9 | import pl.piomin.samples.spring.graphql.domain.Organization; 10 | import pl.piomin.samples.spring.graphql.repository.DepartmentRepository; 11 | import pl.piomin.samples.spring.graphql.repository.EmployeeRepository; 12 | import pl.piomin.samples.spring.graphql.repository.OrganizationRepository; 13 | 14 | @DgsComponent 15 | public class EmployeeMutation { 16 | 17 | DepartmentRepository departmentRepository; 18 | EmployeeRepository employeeRepository; 19 | OrganizationRepository organizationRepository; 20 | 21 | EmployeeMutation(DepartmentRepository departmentRepository, EmployeeRepository employeeRepository, OrganizationRepository organizationRepository) { 22 | this.departmentRepository = departmentRepository; 23 | this.employeeRepository = employeeRepository; 24 | this.organizationRepository = organizationRepository; 25 | } 26 | 27 | @DgsData(parentType = "MutationResolver", field = "newEmployee") 28 | public Employee addEmployee(@InputArgument("employee") EmployeeInput employeeInput) { 29 | Department department = departmentRepository.findById(employeeInput.getDepartmentId()).orElseThrow(); 30 | Organization organization = organizationRepository.findById(employeeInput.getOrganizationId()).orElseThrow(); 31 | return employeeRepository.save(new Employee(null, employeeInput.getFirstName(), employeeInput.getLastName(), 32 | employeeInput.getPosition(), employeeInput.getAge(), employeeInput.getSalary(), 33 | department, organization)); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/fetcher/OrganizationFetcher.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.fetcher; 2 | 3 | import com.netflix.graphql.dgs.DgsComponent; 4 | import com.netflix.graphql.dgs.DgsData; 5 | import com.netflix.graphql.dgs.InputArgument; 6 | import com.netflix.graphql.dgs.exceptions.DgsEntityNotFoundException; 7 | import pl.piomin.samples.spring.graphql.domain.Organization; 8 | import pl.piomin.samples.spring.graphql.repository.OrganizationRepository; 9 | 10 | @DgsComponent 11 | public class OrganizationFetcher { 12 | 13 | private OrganizationRepository repository; 14 | 15 | OrganizationFetcher(OrganizationRepository repository) { 16 | this.repository = repository; 17 | } 18 | 19 | @DgsData(parentType = "QueryResolver", field = "organizations") 20 | public Iterable findAll() { 21 | return repository.findAll(); 22 | } 23 | 24 | @DgsData(parentType = "QueryResolver", field = "organization") 25 | public Organization findById(@InputArgument("id") Integer id) { 26 | return repository.findById(id).orElseThrow(DgsEntityNotFoundException::new); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/fetcher/OrganizationMutation.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.fetcher; 2 | 3 | import com.netflix.graphql.dgs.DgsComponent; 4 | import com.netflix.graphql.dgs.DgsData; 5 | import com.netflix.graphql.dgs.InputArgument; 6 | import pl.piomin.samples.spring.graphql.domain.Organization; 7 | import pl.piomin.samples.spring.graphql.domain.OrganizationInput; 8 | import pl.piomin.samples.spring.graphql.repository.OrganizationRepository; 9 | 10 | @DgsComponent 11 | public class OrganizationMutation { 12 | 13 | OrganizationRepository repository; 14 | 15 | OrganizationMutation(OrganizationRepository repository) { 16 | this.repository = repository; 17 | } 18 | 19 | @DgsData(parentType = "MutationResolver", field = "newOrganization") 20 | public Organization newOrganization(@InputArgument("organization") OrganizationInput organizationInput) { 21 | return repository.save(new Organization(null, organizationInput.getName(), null, null)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/filter/EmployeeFilter.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.filter; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class EmployeeFilter { 7 | private FilterField salary; 8 | private FilterField age; 9 | private FilterField position; 10 | } 11 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/filter/FilterField.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.filter; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.criteria.CriteriaBuilder; 6 | import javax.persistence.criteria.Path; 7 | import javax.persistence.criteria.Predicate; 8 | 9 | @Data 10 | public class FilterField { 11 | private String operator; 12 | private String value; 13 | 14 | public Predicate generateCriteria(CriteriaBuilder builder, Path field) { 15 | try { 16 | int v = Integer.parseInt(value); 17 | switch (operator) { 18 | case "lt": return builder.lt(field, v); 19 | case "le": return builder.le(field, v); 20 | case "gt": return builder.gt(field, v); 21 | case "ge": return builder.ge(field, v); 22 | case "eq": return builder.equal(field, v); 23 | } 24 | } catch (NumberFormatException e) { 25 | switch (operator) { 26 | case "endsWith": return builder.like(field, "%" + value); 27 | case "startsWith": return builder.like(field, value + "%"); 28 | case "contains": return builder.like(field, "%" + value + "%"); 29 | case "eq": return builder.equal(field, value); 30 | } 31 | } 32 | 33 | return null; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/repository/DepartmentRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 4 | import org.springframework.data.repository.CrudRepository; 5 | import pl.piomin.samples.spring.graphql.domain.Department; 6 | 7 | public interface DepartmentRepository extends CrudRepository, 8 | JpaSpecificationExecutor { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/repository/EmployeeRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 4 | import org.springframework.data.repository.CrudRepository; 5 | import pl.piomin.samples.spring.graphql.domain.Employee; 6 | 7 | public interface EmployeeRepository extends CrudRepository, 8 | JpaSpecificationExecutor { 9 | } 10 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/java/pl/piomin/samples/spring/graphql/repository/OrganizationRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql.repository; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | import pl.piomin.samples.spring.graphql.domain.Organization; 5 | 6 | public interface OrganizationRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: sample-spring-boot-graphql 4 | datasource: 5 | url: jdbc:h2:mem:testdb 6 | driverClassName: org.h2.Driver 7 | username: sa 8 | password: password 9 | hikari: 10 | connection-timeout: 2000 11 | initialization-fail-timeout: 0 12 | jpa: 13 | database-platform: org.hibernate.dialect.H2Dialect 14 | properties: 15 | hibernate: 16 | show_sql: true 17 | format_sql: true 18 | enable_lazy_load_no_trans: true -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | insert into organization (name) values ('Test1'); 2 | insert into organization (name) values ('Test2'); 3 | insert into organization (name) values ('Test3'); 4 | insert into organization (name) values ('Test4'); 5 | insert into organization (name) values ('Test5'); 6 | insert into department (name, organization_id) values ('Test1', 1); 7 | insert into department (name, organization_id) values ('Test2', 1); 8 | insert into department (name, organization_id) values ('Test3', 1); 9 | insert into department (name, organization_id) values ('Test4', 2); 10 | insert into department (name, organization_id) values ('Test5', 2); 11 | insert into department (name, organization_id) values ('Test6', 3); 12 | insert into department (name, organization_id) values ('Test7', 4); 13 | insert into department (name, organization_id) values ('Test8', 5); 14 | insert into department (name, organization_id) values ('Test9', 5); 15 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('John', 'Smith', 'Developer', 10000, 30, 1, 1); 16 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Adam', 'Hamilton', 'Developer', 12000, 35, 1, 1); 17 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Tracy', 'Smith', 'Architect', 15000, 40, 1, 1); 18 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Lucy', 'Kim', 'Developer', 13000, 25, 2, 1); 19 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Peter', 'Wright', 'Director', 50000, 50, 4, 2); 20 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Alan', 'Murray', 'Developer', 20000, 37, 4, 2); 21 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Pamela', 'Anderson', 'Analyst', 7000, 27, 4, 2); 22 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/resources/schema/department.graphqls: -------------------------------------------------------------------------------- 1 | type QueryResolver { 2 | departments: [Department] 3 | department(id: ID!): Department! 4 | } 5 | 6 | type MutationResolver { 7 | newDepartment(department: DepartmentInput!): Department 8 | } 9 | 10 | input DepartmentInput { 11 | name: String! 12 | organizationId: Int 13 | } 14 | 15 | type Department { 16 | id: ID! 17 | name: String! 18 | organization: Organization 19 | employees: [Employee] 20 | } -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/resources/schema/employee.graphqls: -------------------------------------------------------------------------------- 1 | extend type QueryResolver { 2 | employees: [Employee] 3 | employeesWithFilter(filter: EmployeeFilter): [Employee] 4 | employee(id: ID!): Employee! 5 | } 6 | 7 | extend type MutationResolver { 8 | newEmployee(employee: EmployeeInput!): Employee 9 | } 10 | 11 | input EmployeeInput { 12 | firstName: String! 13 | lastName: String! 14 | position: String! 15 | salary: Int 16 | age: Int 17 | organizationId: Int! 18 | departmentId: Int! 19 | } 20 | 21 | type Employee { 22 | id: ID! 23 | firstName: String! 24 | lastName: String! 25 | position: String! 26 | salary: Int 27 | age: Int 28 | department: Department 29 | organization: Organization 30 | } 31 | 32 | input EmployeeFilter { 33 | salary: FilterField 34 | age: FilterField 35 | position: FilterField 36 | } 37 | 38 | input FilterField { 39 | operator: String! 40 | value: String! 41 | } 42 | 43 | schema { 44 | query: QueryResolver 45 | mutation: MutationResolver 46 | } -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/main/resources/schema/organization.graphqls: -------------------------------------------------------------------------------- 1 | extend type QueryResolver { 2 | organizations: [Organization] 3 | organization(id: ID!): Organization! 4 | } 5 | 6 | extend type MutationResolver { 7 | newOrganization(organization: OrganizationInput!): Organization 8 | } 9 | 10 | input OrganizationInput { 11 | name: String! 12 | } 13 | 14 | type Organization { 15 | id: ID! 16 | name: String! 17 | employees: [Employee] 18 | departments: [Department] 19 | } -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/test/java/pl/piomin/samples/spring/graphql/DepartmentFetcherTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.netflix.graphql.dgs.DgsQueryExecutor; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.spring.graphql.domain.Department; 9 | 10 | @SpringBootTest 11 | public class DepartmentFetcherTests { 12 | 13 | @Autowired 14 | DgsQueryExecutor executor; 15 | 16 | @Test 17 | void findAll() { 18 | String query = "{ departments { id name } }"; 19 | Department[] departments = executor 20 | .executeAndExtractJsonPathAsObject(query, "data.departments[*]", Department[].class); 21 | Assertions.assertTrue(departments.length > 0); 22 | Assertions.assertNotNull(departments[0].getId()); 23 | Assertions.assertNotNull(departments[0].getName()); 24 | } 25 | 26 | @Test 27 | void findById() { 28 | String query = "{ department(id: 1) { id name organization { id } } }"; 29 | Department department = executor 30 | .executeAndExtractJsonPathAsObject(query, "data.department", Department.class); 31 | Assertions.assertNotNull(department); 32 | Assertions.assertNotNull(department.getId()); 33 | Assertions.assertNotNull(department.getOrganization()); 34 | Assertions.assertNotNull(department.getOrganization().getId()); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/test/java/pl/piomin/samples/spring/graphql/DepartmentMutationTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.netflix.graphql.dgs.DgsQueryExecutor; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.spring.graphql.domain.Department; 9 | import pl.piomin.samples.spring.graphql.domain.Employee; 10 | 11 | @SpringBootTest 12 | public class DepartmentMutationTests { 13 | 14 | @Autowired 15 | DgsQueryExecutor executor; 16 | 17 | @Test 18 | void addDepartment() { 19 | String query = "mutation { newDepartment(department: { name: \"Test10\" organizationId: 1}) { id } }"; 20 | Department department = executor 21 | .executeAndExtractJsonPathAsObject(query, "data.newDepartment", Department.class); 22 | Assertions.assertNotNull(department); 23 | Assertions.assertNotNull(department.getId()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/test/java/pl/piomin/samples/spring/graphql/EmployeeFetcherTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.netflix.graphql.dgs.DgsQueryExecutor; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.spring.graphql.domain.Employee; 9 | 10 | @SpringBootTest 11 | public class EmployeeFetcherTests { 12 | 13 | @Autowired 14 | DgsQueryExecutor executor; 15 | 16 | @Test 17 | void findAll() { 18 | String query = "{ employees { id firstName lastName salary } }"; 19 | Employee[] employees = executor 20 | .executeAndExtractJsonPathAsObject(query, "data.employees[*]", Employee[].class); 21 | Assertions.assertTrue(employees.length > 0); 22 | Assertions.assertNotNull(employees[0].getId()); 23 | Assertions.assertNotNull(employees[0].getFirstName()); 24 | } 25 | 26 | @Test 27 | void findById() { 28 | String query = "{ employee(id: 1) { id firstName lastName salary } }"; 29 | Employee employee = executor 30 | .executeAndExtractJsonPathAsObject(query, "data.employee", Employee.class); 31 | Assertions.assertNotNull(employee); 32 | Assertions.assertNotNull(employee.getId()); 33 | Assertions.assertNotNull(employee.getFirstName()); 34 | } 35 | 36 | @Test 37 | void findWithFilter() { 38 | String query = "{ employeesWithFilter(filter: { salary: { operator: \"gt\" value: \"12000\" } }) { id firstName lastName salary } }"; 39 | Employee[] employees = executor 40 | .executeAndExtractJsonPathAsObject(query, "data.employeesWithFilter[*]", Employee[].class); 41 | Assertions.assertTrue(employees.length > 0); 42 | Assertions.assertNotNull(employees[0].getId()); 43 | Assertions.assertNotNull(employees[0].getFirstName()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/test/java/pl/piomin/samples/spring/graphql/EmployeeMutationTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.netflix.graphql.dgs.DgsQueryExecutor; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.spring.graphql.domain.Employee; 9 | 10 | @SpringBootTest 11 | public class EmployeeMutationTests { 12 | 13 | @Autowired 14 | DgsQueryExecutor executor; 15 | 16 | @Test 17 | void addEmployee() { 18 | String query = "mutation { newEmployee(employee: { firstName: \"John\" lastName: \"Wick\" position: \"developer\" salary: 10000 age: 20 departmentId: 1 organizationId: 1}) { id } }"; 19 | Employee employee = executor 20 | .executeAndExtractJsonPathAsObject(query, "data.newEmployee", Employee.class); 21 | Assertions.assertNotNull(employee); 22 | Assertions.assertNotNull(employee.getId()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/test/java/pl/piomin/samples/spring/graphql/OrganizationFetcherTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.netflix.graphql.dgs.DgsQueryExecutor; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.spring.graphql.domain.Organization; 9 | 10 | @SpringBootTest 11 | public class OrganizationFetcherTests { 12 | 13 | @Autowired 14 | DgsQueryExecutor executor; 15 | 16 | @Test 17 | void findAll() { 18 | String query = "{ organizations { id name } }"; 19 | Organization[] organizations = executor 20 | .executeAndExtractJsonPathAsObject(query, "data.organizations[*]", Organization[].class); 21 | Assertions.assertTrue(organizations.length > 0); 22 | Assertions.assertNotNull(organizations[0].getId()); 23 | Assertions.assertNotNull(organizations[0].getName()); 24 | } 25 | 26 | @Test 27 | void findById() { 28 | String query = "{ organization(id: 1) { id name departments { id } } }"; 29 | Organization organization = executor 30 | .executeAndExtractJsonPathAsObject(query, "data.organization", Organization.class); 31 | Assertions.assertNotNull(organization); 32 | Assertions.assertNotNull(organization.getId()); 33 | Assertions.assertTrue(organization.getDepartments().size() > 0); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /sample-app-netflix-dgs/src/test/java/pl/piomin/samples/spring/graphql/OrganizationMutationTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.samples.spring.graphql; 2 | 3 | import com.netflix.graphql.dgs.DgsQueryExecutor; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import pl.piomin.samples.spring.graphql.domain.Department; 9 | import pl.piomin.samples.spring.graphql.domain.Organization; 10 | 11 | @SpringBootTest 12 | public class OrganizationMutationTests { 13 | 14 | @Autowired 15 | DgsQueryExecutor executor; 16 | 17 | @Test 18 | void addOrganization() { 19 | String query = "mutation { newOrganization(organization: { name: \"Test6\"}) { id } }"; 20 | Organization organization = executor 21 | .executeAndExtractJsonPathAsObject(query, "data.newOrganization", Organization.class); 22 | Assertions.assertNotNull(organization); 23 | Assertions.assertNotNull(organization.getId()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.7.7 9 | 10 | 11 | 4.0.0 12 | 13 | sample-app-spring-graphql 14 | 15 | 16 | 11 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-graphql 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-data-jpa 31 | 32 | 33 | com.h2database 34 | h2 35 | runtime 36 | 37 | 38 | org.projectlombok 39 | lombok 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-test 44 | test 45 | 46 | 47 | org.springframework.graphql 48 | spring-graphql-test 49 | test 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-maven-plugin 58 | 59 | 60 | org.jacoco 61 | jacoco-maven-plugin 62 | 0.8.11 63 | 64 | 65 | 66 | prepare-agent 67 | 68 | 69 | 70 | report 71 | test 72 | 73 | report 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/SampleSpringBootGraphQL.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SampleSpringBootGraphQL { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SampleSpringBootGraphQL.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/domain/Department.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | import java.util.Set; 10 | 11 | @Entity 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 16 | public class Department { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | @EqualsAndHashCode.Include 20 | private Integer id; 21 | private String name; 22 | @OneToMany(mappedBy = "department") 23 | private Set employees; 24 | @ManyToOne(fetch = FetchType.LAZY) 25 | private Organization organization; 26 | } 27 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/domain/DepartmentInput.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | @NoArgsConstructor 10 | public class DepartmentInput { 11 | private String name; 12 | private Integer organizationId; 13 | } 14 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/domain/Employee.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | 10 | @Entity 11 | @Data 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 15 | public class Employee { 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | @EqualsAndHashCode.Include 19 | private Integer id; 20 | private String firstName; 21 | private String lastName; 22 | private String position; 23 | private int salary; 24 | private int age; 25 | @ManyToOne(fetch = FetchType.LAZY) 26 | private Department department; 27 | @ManyToOne(fetch = FetchType.LAZY) 28 | private Organization organization; 29 | } 30 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/domain/EmployeeInput.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class EmployeeInput { 11 | private String firstName; 12 | private String lastName; 13 | private String position; 14 | private int salary; 15 | private int age; 16 | private Integer departmentId; 17 | private Integer organizationId; 18 | } 19 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/domain/Organization.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | import java.util.Set; 10 | 11 | @Entity 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 16 | public class Organization { 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | @EqualsAndHashCode.Include 20 | private Integer id; 21 | private String name; 22 | @OneToMany(mappedBy = "organization") 23 | private Set departments; 24 | @OneToMany(mappedBy = "organization") 25 | private Set employees; 26 | } 27 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/domain/OrganizationInput.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class OrganizationInput { 11 | private String name; 12 | } 13 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/filter/EmployeeFilter.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.filter; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class EmployeeFilter { 7 | private FilterField salary; 8 | private FilterField age; 9 | private FilterField position; 10 | } 11 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/filter/FilterField.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.filter; 2 | 3 | import lombok.Data; 4 | 5 | import javax.persistence.criteria.CriteriaBuilder; 6 | import javax.persistence.criteria.Path; 7 | import javax.persistence.criteria.Predicate; 8 | 9 | @Data 10 | public class FilterField { 11 | private String operator; 12 | private String value; 13 | 14 | public Predicate generateCriteria(CriteriaBuilder builder, Path field) { 15 | try { 16 | int v = Integer.parseInt(value); 17 | switch (operator) { 18 | case "lt": return builder.lt(field, v); 19 | case "le": return builder.le(field, v); 20 | case "gt": return builder.gt(field, v); 21 | case "ge": return builder.ge(field, v); 22 | case "eq": return builder.equal(field, v); 23 | } 24 | } catch (NumberFormatException e) { 25 | switch (operator) { 26 | case "endsWith": return builder.like(field, "%" + value); 27 | case "startsWith": return builder.like(field, value + "%"); 28 | case "contains": return builder.like(field, "%" + value + "%"); 29 | case "eq": return builder.equal(field, value); 30 | } 31 | } 32 | 33 | return null; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/repository/DepartmentRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 4 | import org.springframework.data.repository.CrudRepository; 5 | import pl.piomin.sample.spring.graphql.domain.Department; 6 | 7 | public interface DepartmentRepository extends CrudRepository, 8 | JpaSpecificationExecutor { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/repository/EmployeeRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 4 | import org.springframework.data.repository.CrudRepository; 5 | import pl.piomin.sample.spring.graphql.domain.Employee; 6 | 7 | public interface EmployeeRepository extends CrudRepository, 8 | JpaSpecificationExecutor { 9 | } 10 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/repository/OrganizationRepository.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaSpecificationExecutor; 4 | import org.springframework.data.repository.CrudRepository; 5 | import pl.piomin.sample.spring.graphql.domain.Department; 6 | import pl.piomin.sample.spring.graphql.domain.Organization; 7 | 8 | public interface OrganizationRepository extends CrudRepository, 9 | JpaSpecificationExecutor { 10 | } 11 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/resolver/DepartmentController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.resolver; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import graphql.schema.DataFetchingFieldSelectionSet; 5 | import org.springframework.data.jpa.domain.Specification; 6 | import org.springframework.graphql.data.method.annotation.Argument; 7 | import org.springframework.graphql.data.method.annotation.MutationMapping; 8 | import org.springframework.graphql.data.method.annotation.QueryMapping; 9 | import org.springframework.stereotype.Controller; 10 | import pl.piomin.sample.spring.graphql.domain.Department; 11 | import pl.piomin.sample.spring.graphql.domain.DepartmentInput; 12 | import pl.piomin.sample.spring.graphql.domain.Employee; 13 | import pl.piomin.sample.spring.graphql.domain.Organization; 14 | import pl.piomin.sample.spring.graphql.repository.DepartmentRepository; 15 | import pl.piomin.sample.spring.graphql.repository.OrganizationRepository; 16 | 17 | import javax.persistence.criteria.Fetch; 18 | import javax.persistence.criteria.Join; 19 | import javax.persistence.criteria.JoinType; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.NoSuchElementException; 23 | 24 | @Controller 25 | public class DepartmentController { 26 | 27 | DepartmentRepository departmentRepository; 28 | OrganizationRepository organizationRepository; 29 | 30 | DepartmentController(DepartmentRepository departmentRepository, OrganizationRepository organizationRepository) { 31 | this.departmentRepository = departmentRepository; 32 | this.organizationRepository = organizationRepository; 33 | } 34 | 35 | @MutationMapping 36 | public Department newDepartment(@Argument DepartmentInput department) { 37 | Organization organization = organizationRepository.findById(department.getOrganizationId()).get(); 38 | return departmentRepository.save(new Department(null, department.getName(), null, organization)); 39 | } 40 | 41 | @QueryMapping 42 | public Iterable departments(DataFetchingEnvironment environment) { 43 | DataFetchingFieldSelectionSet s = environment.getSelectionSet(); 44 | List> specifications = new ArrayList<>(); 45 | if (s.contains("employees") && !s.contains("organization")) 46 | return departmentRepository.findAll(fetchEmployees()); 47 | else if (!s.contains("employees") && s.contains("organization")) 48 | return departmentRepository.findAll(fetchOrganization()); 49 | else if (s.contains("employees") && s.contains("organization")) 50 | return departmentRepository.findAll(fetchEmployees().and(fetchOrganization())); 51 | else 52 | return departmentRepository.findAll(); 53 | } 54 | 55 | @QueryMapping 56 | public Department department(@Argument Integer id, DataFetchingEnvironment environment) { 57 | Specification spec = byId(id); 58 | DataFetchingFieldSelectionSet selectionSet = environment.getSelectionSet(); 59 | if (selectionSet.contains("employees")) 60 | spec = spec.and(fetchEmployees()); 61 | if (selectionSet.contains("organization")) 62 | spec = spec.and(fetchOrganization()); 63 | return departmentRepository.findOne(spec).orElseThrow(NoSuchElementException::new); 64 | } 65 | 66 | private Specification fetchOrganization() { 67 | return (root, query, builder) -> { 68 | Fetch f = root.fetch("organization", JoinType.LEFT); 69 | Join join = (Join) f; 70 | return join.getOn(); 71 | }; 72 | } 73 | 74 | private Specification fetchEmployees() { 75 | return (root, query, builder) -> { 76 | Fetch f = root.fetch("employees", JoinType.LEFT); 77 | Join join = (Join) f; 78 | return join.getOn(); 79 | }; 80 | } 81 | 82 | private Specification byId(Integer id) { 83 | return (root, query, builder) -> builder.equal(root.get("id"), id); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/resolver/EmployeeController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.resolver; 2 | 3 | import org.springframework.data.jpa.domain.Specification; 4 | import org.springframework.graphql.data.method.annotation.Argument; 5 | import org.springframework.graphql.data.method.annotation.MutationMapping; 6 | import org.springframework.graphql.data.method.annotation.QueryMapping; 7 | import org.springframework.stereotype.Controller; 8 | import pl.piomin.sample.spring.graphql.domain.Department; 9 | import pl.piomin.sample.spring.graphql.domain.Employee; 10 | import pl.piomin.sample.spring.graphql.domain.EmployeeInput; 11 | import pl.piomin.sample.spring.graphql.domain.Organization; 12 | import pl.piomin.sample.spring.graphql.filter.EmployeeFilter; 13 | import pl.piomin.sample.spring.graphql.filter.FilterField; 14 | import pl.piomin.sample.spring.graphql.repository.DepartmentRepository; 15 | import pl.piomin.sample.spring.graphql.repository.EmployeeRepository; 16 | import pl.piomin.sample.spring.graphql.repository.OrganizationRepository; 17 | 18 | @Controller 19 | public class EmployeeController { 20 | 21 | DepartmentRepository departmentRepository; 22 | EmployeeRepository employeeRepository; 23 | OrganizationRepository organizationRepository; 24 | 25 | EmployeeController(DepartmentRepository departmentRepository, EmployeeRepository employeeRepository, OrganizationRepository organizationRepository) { 26 | this.departmentRepository = departmentRepository; 27 | this.employeeRepository = employeeRepository; 28 | this.organizationRepository = organizationRepository; 29 | } 30 | 31 | @QueryMapping 32 | public Iterable employees() { 33 | return employeeRepository.findAll(); 34 | } 35 | 36 | @QueryMapping 37 | public Employee employee(@Argument Integer id) { 38 | return employeeRepository.findById(id).orElseThrow(); 39 | } 40 | 41 | @MutationMapping 42 | public Employee newEmployee(@Argument EmployeeInput employee) { 43 | Department department = departmentRepository.findById(employee.getDepartmentId()).get(); 44 | Organization organization = organizationRepository.findById(employee.getOrganizationId()).get(); 45 | return employeeRepository.save(new Employee(null, employee.getFirstName(), employee.getLastName(), 46 | employee.getPosition(), employee.getAge(), employee.getSalary(), 47 | department, organization)); 48 | } 49 | 50 | @QueryMapping 51 | public Iterable employeesWithFilter(@Argument EmployeeFilter filter) { 52 | Specification spec = null; 53 | if (filter.getSalary() != null) 54 | spec = bySalary(filter.getSalary()); 55 | if (filter.getAge() != null) 56 | spec = (spec == null ? byAge(filter.getAge()) : spec.and(byAge(filter.getAge()))); 57 | if (filter.getPosition() != null) 58 | spec = (spec == null ? byPosition(filter.getPosition()) : 59 | spec.and(byPosition(filter.getPosition()))); 60 | if (spec != null) 61 | return employeeRepository.findAll(spec); 62 | else 63 | return employeeRepository.findAll(); 64 | } 65 | 66 | private Specification bySalary(FilterField filterField) { 67 | return (root, query, builder) -> filterField.generateCriteria(builder, root.get("salary")); 68 | } 69 | 70 | private Specification byAge(FilterField filterField) { 71 | return (root, query, builder) -> filterField.generateCriteria(builder, root.get("age")); 72 | } 73 | 74 | private Specification byPosition(FilterField filterField) { 75 | return (root, query, builder) -> filterField.generateCriteria(builder, root.get("position")); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/java/pl/piomin/sample/spring/graphql/resolver/OrganizationController.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql.resolver; 2 | 3 | import graphql.schema.DataFetchingEnvironment; 4 | import graphql.schema.DataFetchingFieldSelectionSet; 5 | import org.springframework.data.jpa.domain.Specification; 6 | import org.springframework.graphql.data.method.annotation.Argument; 7 | import org.springframework.graphql.data.method.annotation.MutationMapping; 8 | import org.springframework.graphql.data.method.annotation.QueryMapping; 9 | import org.springframework.stereotype.Controller; 10 | import pl.piomin.sample.spring.graphql.domain.Department; 11 | import pl.piomin.sample.spring.graphql.domain.Employee; 12 | import pl.piomin.sample.spring.graphql.domain.Organization; 13 | import pl.piomin.sample.spring.graphql.domain.OrganizationInput; 14 | import pl.piomin.sample.spring.graphql.repository.OrganizationRepository; 15 | 16 | import javax.persistence.criteria.Fetch; 17 | import javax.persistence.criteria.Join; 18 | import javax.persistence.criteria.JoinType; 19 | 20 | @Controller 21 | public class OrganizationController { 22 | 23 | OrganizationRepository repository; 24 | 25 | OrganizationController(OrganizationRepository repository) { 26 | this.repository = repository; 27 | } 28 | 29 | @MutationMapping 30 | public Organization newOrganization(@Argument OrganizationInput organization) { 31 | return repository.save(new Organization(null, organization.getName(), null, null)); 32 | } 33 | 34 | @QueryMapping 35 | public Iterable organizations() { 36 | return repository.findAll(); 37 | } 38 | 39 | @QueryMapping 40 | public Organization organization(@Argument Integer id, DataFetchingEnvironment environment) { 41 | Specification spec = byId(id); 42 | DataFetchingFieldSelectionSet selectionSet = environment.getSelectionSet(); 43 | if (selectionSet.contains("employees")) 44 | spec = spec.and(fetchEmployees()); 45 | if (selectionSet.contains("departments")) 46 | spec = spec.and(fetchDepartments()); 47 | return repository.findOne(spec).orElseThrow(); 48 | } 49 | 50 | private Specification fetchDepartments() { 51 | return (root, query, builder) -> { 52 | Fetch f = root.fetch("departments", JoinType.LEFT); 53 | Join join = (Join) f; 54 | return join.getOn(); 55 | }; 56 | } 57 | 58 | private Specification fetchEmployees() { 59 | return (root, query, builder) -> { 60 | Fetch f = root.fetch("employees", JoinType.LEFT); 61 | Join join = (Join) f; 62 | return join.getOn(); 63 | }; 64 | } 65 | 66 | private Specification byId(Integer id) { 67 | return (root, query, builder) -> builder.equal(root.get("id"), id); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: sample-app-spring-graphql 4 | datasource: 5 | url: jdbc:h2:mem:testdb 6 | driverClassName: org.h2.Driver 7 | username: sa 8 | password: password 9 | hikari: 10 | connection-timeout: 2000 11 | initialization-fail-timeout: 0 12 | graphql: 13 | graphiql: 14 | enabled: true 15 | jpa: 16 | show-sql: true 17 | database-platform: org.hibernate.dialect.H2Dialect 18 | properties: 19 | hibernate: 20 | format_sql: true -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/resources/graphql/department.graphqls: -------------------------------------------------------------------------------- 1 | type Query { 2 | departments: [Department] 3 | department(id: ID!): Department! 4 | } 5 | 6 | type Mutation { 7 | newDepartment(department: DepartmentInput!): Department 8 | } 9 | 10 | input DepartmentInput { 11 | name: String! 12 | organizationId: Int 13 | } 14 | 15 | type Department { 16 | id: ID! 17 | name: String! 18 | organization: Organization 19 | employees: [Employee] 20 | } -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/resources/graphql/employee.graphqls: -------------------------------------------------------------------------------- 1 | extend type Query { 2 | employees: [Employee] 3 | employeesWithFilter(filter: EmployeeFilter): [Employee] 4 | employee(id: ID!): Employee! 5 | } 6 | 7 | extend type Mutation { 8 | newEmployee(employee: EmployeeInput!): Employee 9 | } 10 | 11 | input EmployeeInput { 12 | firstName: String! 13 | lastName: String! 14 | position: String! 15 | salary: Int 16 | age: Int 17 | organizationId: Int! 18 | departmentId: Int! 19 | } 20 | 21 | type Employee { 22 | id: ID! 23 | firstName: String! 24 | lastName: String! 25 | position: String! 26 | salary: Int 27 | age: Int 28 | department: Department 29 | organization: Organization 30 | } 31 | 32 | input EmployeeFilter { 33 | salary: FilterField 34 | age: FilterField 35 | position: FilterField 36 | } 37 | 38 | input FilterField { 39 | operator: String! 40 | value: String! 41 | } 42 | 43 | #schema { 44 | # query: QueryResolver 45 | # mutation: MutationResolver 46 | #} -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/resources/graphql/organization.graphqls: -------------------------------------------------------------------------------- 1 | extend type Query { 2 | organizations: [Organization] 3 | organization(id: ID!): Organization! 4 | } 5 | 6 | extend type Mutation { 7 | newOrganization(organization: OrganizationInput!): Organization 8 | } 9 | 10 | input OrganizationInput { 11 | name: String! 12 | } 13 | 14 | type Organization { 15 | id: ID! 16 | name: String! 17 | employees: [Employee] 18 | departments: [Department] 19 | } -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/main/resources/import.sql: -------------------------------------------------------------------------------- 1 | insert into organization (name) values ('Test1'); 2 | insert into organization (name) values ('Test2'); 3 | insert into organization (name) values ('Test3'); 4 | insert into organization (name) values ('Test4'); 5 | insert into organization (name) values ('Test5'); 6 | insert into department (name, organization_id) values ('Test1', 1); 7 | insert into department (name, organization_id) values ('Test2', 1); 8 | insert into department (name, organization_id) values ('Test3', 1); 9 | insert into department (name, organization_id) values ('Test4', 2); 10 | insert into department (name, organization_id) values ('Test5', 2); 11 | insert into department (name, organization_id) values ('Test6', 3); 12 | insert into department (name, organization_id) values ('Test7', 4); 13 | insert into department (name, organization_id) values ('Test8', 5); 14 | insert into department (name, organization_id) values ('Test9', 5); 15 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('John', 'Smith', 'Developer', 10000, 30, 1, 1); 16 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Adam', 'Hamilton', 'Developer', 12000, 35, 1, 1); 17 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Tracy', 'Smith', 'Architect', 15000, 40, 1, 1); 18 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Lucy', 'Kim', 'Developer', 13000, 25, 2, 1); 19 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Peter', 'Wright', 'Director', 50000, 50, 4, 2); 20 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Alan', 'Murray', 'Developer', 20000, 37, 4, 2); 21 | insert into employee (first_name, last_name, position, salary, age, department_id, organization_id) values ('Pamela', 'Anderson', 'Analyst', 7000, 27, 4, 2); 22 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/test/java/pl/piomin/sample/spring/graphql/DepartmentControllerTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureGraphQlTester; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.graphql.test.tester.GraphQlTester; 9 | import pl.piomin.sample.spring.graphql.domain.Department; 10 | 11 | import java.util.List; 12 | 13 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) 14 | @AutoConfigureGraphQlTester 15 | public class DepartmentControllerTests { 16 | 17 | @Autowired 18 | private GraphQlTester tester; 19 | 20 | @Test 21 | void addDepartment() { 22 | String query = "mutation { newDepartment(department: { name: \"Test10\" organizationId: 1}) { id } }"; 23 | Department department = tester.document(query) 24 | .execute() 25 | .path("data.newDepartment") 26 | .entity(Department.class) 27 | .get(); 28 | Assertions.assertNotNull(department); 29 | Assertions.assertNotNull(department.getId()); 30 | } 31 | 32 | @Test 33 | void findAll() { 34 | String query = "{ departments { id name } }"; 35 | List departments = tester.document(query) 36 | .execute() 37 | .path("data.departments[*]") 38 | .entityList(Department.class) 39 | .get(); 40 | Assertions.assertTrue(departments.size() > 0); 41 | Assertions.assertNotNull(departments.get(0).getId()); 42 | Assertions.assertNotNull(departments.get(0).getName()); 43 | } 44 | 45 | @Test 46 | void findById() { 47 | String query = "{ department(id: 1) { id name organization { id } } }"; 48 | Department department = tester.document(query) 49 | .execute() 50 | .path("data.department") 51 | .entity(Department.class) 52 | .get(); 53 | Assertions.assertNotNull(department); 54 | Assertions.assertNotNull(department.getId()); 55 | Assertions.assertNotNull(department.getOrganization()); 56 | Assertions.assertNotNull(department.getOrganization().getId()); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/test/java/pl/piomin/sample/spring/graphql/EmployeeControllerTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureGraphQlTester; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.graphql.test.tester.GraphQlTester; 9 | import pl.piomin.sample.spring.graphql.domain.Employee; 10 | 11 | import java.util.List; 12 | 13 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) 14 | @AutoConfigureGraphQlTester 15 | public class EmployeeControllerTests { 16 | 17 | @Autowired 18 | private GraphQlTester tester; 19 | 20 | @Test 21 | void addEmployee() { 22 | String query = "mutation { newEmployee(employee: { firstName: \"John\" lastName: \"Wick\" position: \"developer\" salary: 10000 age: 20 departmentId: 1 organizationId: 1}) { id } }"; 23 | Employee employee = tester.document(query) 24 | .execute() 25 | .path("data.newEmployee") 26 | .entity(Employee.class) 27 | .get(); 28 | Assertions.assertNotNull(employee); 29 | Assertions.assertNotNull(employee.getId()); 30 | } 31 | 32 | @Test 33 | void findAll() { 34 | String query = "{ employees { id firstName lastName salary } }"; 35 | List employees = tester.document(query) 36 | .execute() 37 | .path("data.employees[*]") 38 | .entityList(Employee.class) 39 | .get(); 40 | Assertions.assertTrue(employees.size() > 0); 41 | Assertions.assertNotNull(employees.get(0).getId()); 42 | Assertions.assertNotNull(employees.get(0).getFirstName()); 43 | } 44 | 45 | @Test 46 | void findById() { 47 | String query = "{ employee(id: 1) { id firstName lastName salary } }"; 48 | Employee employee = tester.document(query) 49 | .execute() 50 | .path("data.employee") 51 | .entity(Employee.class) 52 | .get(); 53 | Assertions.assertNotNull(employee); 54 | Assertions.assertNotNull(employee.getId()); 55 | Assertions.assertNotNull(employee.getFirstName()); 56 | } 57 | 58 | @Test 59 | void findWithFilter() { 60 | String query = "{ employeesWithFilter(filter: { salary: { operator: \"gt\" value: \"12000\" } }) { id firstName lastName salary } }"; 61 | List employees = tester.document(query) 62 | .execute() 63 | .path("data.employeesWithFilter[*]") 64 | .entityList(Employee.class) 65 | .get(); 66 | Assertions.assertTrue(employees.size() > 0); 67 | Assertions.assertNotNull(employees.get(0).getId()); 68 | Assertions.assertNotNull(employees.get(0).getFirstName()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /sample-app-spring-graphql/src/test/java/pl/piomin/sample/spring/graphql/OrganizationControllerTests.java: -------------------------------------------------------------------------------- 1 | package pl.piomin.sample.spring.graphql; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureGraphQlTester; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.graphql.test.tester.GraphQlTester; 9 | import pl.piomin.sample.spring.graphql.domain.Organization; 10 | 11 | import java.util.List; 12 | 13 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) 14 | @AutoConfigureGraphQlTester 15 | public class OrganizationControllerTests { 16 | 17 | @Autowired 18 | private GraphQlTester tester; 19 | 20 | @Test 21 | void addOrganization() { 22 | String query = "mutation { newOrganization(organization: { name: \"Test10\" }) { id } }"; 23 | Organization organization = tester.document(query) 24 | .execute() 25 | .path("data.newOrganization") 26 | .entity(Organization.class) 27 | .get(); 28 | Assertions.assertNotNull(organization); 29 | Assertions.assertNotNull(organization.getId()); 30 | } 31 | 32 | @Test 33 | void findAll() { 34 | String query = "{ organizations { id name } }"; 35 | List organizations = tester.document(query) 36 | .execute() 37 | .path("data.organizations[*]") 38 | .entityList(Organization.class) 39 | .get(); 40 | Assertions.assertTrue(organizations.size() > 0); 41 | Assertions.assertNotNull(organizations.get(0).getId()); 42 | Assertions.assertNotNull(organizations.get(0).getName()); 43 | } 44 | 45 | @Test 46 | void findById() { 47 | String query = "{ organization(id: 1) { id name departments { id } } }"; 48 | Organization organization = tester.document(query) 49 | .execute() 50 | .path("data.organization") 51 | .entity(Organization.class) 52 | .get(); 53 | Assertions.assertNotNull(organization); 54 | Assertions.assertNotNull(organization.getId()); 55 | Assertions.assertTrue(organization.getDepartments().size() > 0); 56 | } 57 | 58 | } 59 | --------------------------------------------------------------------------------