├── .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 [](https://twitter.com/piotr_minkowski)
2 |
3 | [](https://circleci.com/gh/piomin/sample-spring-boot-graphql)
4 |
5 | [](https://sonarcloud.io/dashboard?id=piomin_sample-spring-boot-graphql)
6 | [](https://sonarcloud.io/dashboard?id=piomin_sample-spring-boot-graphql)
7 | [](https://sonarcloud.io/dashboard?id=piomin_sample-spring-boot-graphql)
8 | [](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 |
--------------------------------------------------------------------------------