spring-boot Archives - Piotr's TechBlog https://piotrminkowski.com/tag/spring-boot-2/ Java, Spring, Kotlin, microservices, Kubernetes, containers Tue, 13 Jul 2021 12:58:15 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://i0.wp.com/piotrminkowski.com/wp-content/uploads/2020/08/cropped-me-2-tr-x-1.png?fit=32%2C32&ssl=1 spring-boot Archives - Piotr's TechBlog https://piotrminkowski.com/tag/spring-boot-2/ 32 32 181738725 Express JPA Queries as Java Streams https://piotrminkowski.com/2021/07/13/express-jpa-queries-as-java-streams/ https://piotrminkowski.com/2021/07/13/express-jpa-queries-as-java-streams/#comments Tue, 13 Jul 2021 11:00:11 +0000 https://piotrminkowski.com/?p=9949 In this article, you will learn how to use the JPAstreamer library to express your JPA queries with Java streams. I will also show you how to integrate this library with Spring Boot and Spring Data. The idea around it is very simple but at the same time brilliant. The library creates a SQL query […]

The post Express JPA Queries as Java Streams appeared first on Piotr's TechBlog.

]]>
In this article, you will learn how to use the JPAstreamer library to express your JPA queries with Java streams. I will also show you how to integrate this library with Spring Boot and Spring Data. The idea around it is very simple but at the same time brilliant. The library creates a SQL query based on your Java stream. That’s all. I have already mentioned this library on my Twitter account.

jpa-java-streams-twitter

Before we start, let’s take a look at the following picture. It should explain the concept in a simple way. That’s pretty intuitive, right?

jpa-java-streams-table

Source Code

If you would like to try it by yourself, you may always take a look at my source code. In order to do that you need to clone my GitHub repository. Then you should just follow my instructions. Let’s begin.

Dependencies and configuration

As an example, we have a simple Spring Boot application that runs an embedded H2 database and exposes data through a REST API. It also uses Spring Data JPA to interact with the database. But with the JPAstreamer library, this is completely transparent for us. So, in the first step, we need to include the following two dependencies. The first of them adds JPAstreamer while the second integrate it with Spring Boot.

<dependency>
  <groupId>com.speedment.jpastreamer</groupId>
  <artifactId>jpastreamer-core</artifactId>
  <version>1.0.1</version>
</dependency>
<dependency>
  <groupId>com.speedment.jpastreamer.integration.spring</groupId>
  <artifactId>spring-boot-jpastreamer-autoconfigure</artifactId>
  <version>1.0.1</version>
</dependency>

Then, we need to add Spring Boot Web and JPA starters, H2 database, and optionally Lombok.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>com.h2database</groupId>
  <artifactId>h2</artifactId>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.20</version>
</dependency>

I’m using Java 15 for compilation. Because I use Java records for DTO I need to enable preview features. Here’s the plugin responsible for it.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.8.1</version>
  <configuration>
    <release>15</release>
    <compilerArgs>
        --enable-preview
    </compilerArgs>
    <source>15</source>
    <target>15</target>
  </configuration>
</plugin>

The JPAstreamer library generates source code based on your entity model. Then we may use it, for example, to perform filtering or sorting. But we will talk about it in the next part of the article. For now, let’s configure the build process with build-helper-maven-plugin. It generates the source code in the target/generated-sources/annotations directory. If you use IntelliJ it is automatically included as a source folder in your project.

<plugin>
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>build-helper-maven-plugin</artifactId>
  <version>3.2.0</version>
  <executions>
    <execution>
      <phase>generate-sources</phase>
      <goals>
        <goal>add-source</goal>
      </goals>
      <configuration>
        <sources>
          <source>${project.build.directory}/generated-sources/annotations</source>
        </sources>
      </configuration>
    </execution>
  </executions>
</plugin>

Here is a source code generated by JPAstreamer for our entity model.

Entity model for JPA

Let’s take a look at our example entities. Here’s the Employee class. Each employee is assigned to the department and organization.

@Entity
@NoArgsConstructor
@Getter
@Setter
@ToString
public class Employee {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;
   private String name;
   private String position;
   private int salary;
   @ManyToOne(fetch = FetchType.LAZY)
   private Department department;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;
}

Here’s the Department entity.

@Entity
@NoArgsConstructor
@Getter
@Setter
public class Department {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;
   private String name;
   @OneToMany(mappedBy = "department")
   private Set<Employee> employees;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;
}

Here’s the Organization entity.

@Entity
@NoArgsConstructor
@Getter
@Setter
public class Organization {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;
   private String name;
   @OneToMany(mappedBy = "organization")
   private Set<Department> departments;
   @OneToMany(mappedBy = "organization")
   private Set<Employee> employees;
}

We will also use Java records to create DTOs. Here’s a simple DTO for the Employee entity.

public record EmployeeDTO(
   Integer id,
   String name,
   String position,
   int salary
) {
   public EmployeeDTO(Employee emp) {
      this(emp.getId(), emp.getName(), emp.getPosition(), emp.getSalary());
   }
}

We also have a DTO record to express relationship fields.

public record EmployeeWithDetailsDTO(
   Integer id,
   String name,
   String position,
   int salary,
   String organizationName,
   String departmentName
) {
   public EmployeeWithDetailsDTO(Employee emp) {
      this(emp.getId(), emp.getName(), emp.getPosition(), emp.getSalary(),
            emp.getOrganization().getName(),
            emp.getDepartment().getName());
   }
}

Express JPA queries as Java streams

Let’s begin with a simple example. We would like to get all the departments, sort it ascending based on the name field, and then convert it to DTO. We just need to get an instance of JPAstreamer object and invoke a stream() method. Then you do everything else as you would act with standard Java streams.

@GetMapping
public List<DepartmentDTO> findAll() {
   return streamer.stream(Department.class)
        .sorted(Department$.name)
        .map(DepartmentDTO::new)
        .collect(Collectors.toList());
}

Now, we can call the endpoint after starting our Spring Boot application.

$ curl http://localhost:8080/departments
[{"id":4,"name":"aaa"},{"id":3,"name":"bbb"},{"id":2,"name":"ccc"},{"id":1,"name":"ddd"}]

Let’s take a look at something a little bit more advanced. We are going to find employees with salaries greater than an input value, sort them by salaries, and of course map to DTO.

@GetMapping("/greater-than/{salary}")
public List<EmployeeDTO> findBySalaryGreaterThan(@PathVariable("salary") int salary) {
   return streamer.stream(Employee.class)
         .filter(Employee$.salary.greaterThan(salary))
         .sorted(Employee$.salary)
         .map(EmployeeDTO::new)
         .collect(Collectors.toList());
}

Then, we call another endpoint once again.

$ curl http://localhost:8080/employees/greater-than/25000    
[{"id":5,"name":"Test5","position":"Architect","salary":30000},{"id":7,"name":"Test7","position":"Manager","salary":30000},{"id":9,"name":"Test9","position":"Developer","salary":30000}]

We can also perform JPA pagination operations by using skip and limit Java streams methods.

@GetMapping("/offset/{offset}/limit/{limit}")
public List<EmployeeDTO> findAllWithPagination(
      @PathVariable("offset") int offset, 
      @PathVariable("limit") int limit) {
   return streamer.stream(Employee.class)
         .skip(offset)
         .limit(limit)
         .map(EmployeeDTO::new)
         .collect(Collectors.toList());
}

What is important all such operations are performed on the database side. Here’s the SQL query generated for the implementation visible above.

What about relationships between entities? Of course relationships between tables are handled via the JPA provider. In order to perform the JOIN operation with JPAstreamer, we just need to specify the joining stream. By default, it is LEFT JOIN, but we can customize it when calling the joining() method. In the following fragment of code, we join Department and Organization, which are in @ManyToOne relationship with the Employee entity.

@GetMapping("/{id}")
public EmployeeWithDetailsDTO findById(@PathVariable("id") Integer id) {
   return streamer.stream(of(Employee.class)
           .joining(Employee$.department)
           .joining(Employee$.organization))
        .filter(Employee$.id.equal(id))
        .map(EmployeeWithDetailsDTO::new)
        .findFirst()
        .orElseThrow();
}

Of course, we can call many other Java stream methods. In the following fragment of code, we count the number of employees assigned to the particular department.

@GetMapping("/{id}/count-employees")
public long getNumberOfEmployees(@PathVariable("id") Integer id) {
   return streamer.stream(Department.class)
         .filter(Department$.id.equal(id))
         .map(Department::getEmployees)
         .mapToLong(Set::size)
         .sum();
}

And the last example today. We get all the employees assigned to a particular department and map each of them to EmployeeDTO.

@GetMapping("/{id}/employees")
public List<EmployeeDTO> getEmployees(@PathVariable("id") Integer id) {
   return streamer.stream(Department.class)
         .filter(Department$.id.equal(id))
         .map(Department::getEmployees)
         .flatMap(Set::stream)
         .map(EmployeeDTO::new)
         .collect(Collectors.toList());
}

Integration with Spring Boot

We can easily integrate JPAstreamer with Spring Boot and Spring Data JPA. In fact, you don’t have anything more than just include a dependency responsible for integration with Spring. It provides auto-configuration also for Spring Data JPA. Therefore, we just need to inject the JPAStreamer bean into the target service or controller.

@RestController
@RequestMapping("/employees")
public class EmployeeController {

   private final JPAStreamer streamer;

   public EmployeeController(JPAStreamer streamer) {
      this.streamer = streamer;
   }

   @GetMapping("/greater-than/{salary}")
   public List<EmployeeDTO> findBySalaryGreaterThan(
      @PathVariable("salary") int salary) {
   return streamer.stream(Employee.class)
        .filter(Employee$.salary.greaterThan(salary))
        .sorted(Employee$.salary)
        .map(EmployeeDTO::new)
        .collect(Collectors.toList());
   }

   // ...

}

Final Thoughts

The concept around the JPAstreamer library seems to be very interesting. I really like it. The only disadvantage I found is that it sends certain data back to Speedment’s servers for Google Analytics. If you wish to disable this feature, you need to contact their team. To be honest, I’m concerned a little. But it doesn’t change my point of view, that JPAstreamer is a very useful library. If you are interested in topics related to Java streams and collections you may read my article Using Eclipse Collections.

The post Express JPA Queries as Java Streams appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2021/07/13/express-jpa-queries-as-java-streams/feed/ 21 9949
An Advanced GraphQL with Quarkus https://piotrminkowski.com/2021/04/14/advanced-graphql-with-quarkus/ https://piotrminkowski.com/2021/04/14/advanced-graphql-with-quarkus/#comments Wed, 14 Apr 2021 09:46:25 +0000 https://piotrminkowski.com/?p=9672 In this article, you will learn how to create a GraphQL application using the Quarkus framework. Our application will connect to a database, and we will use the Quarkus Panache module as the ORM provider. On the other hand, Quarkus GraphQL support is built on top of the SmallRye GraphQL library. We will discuss some […]

The post An Advanced GraphQL with Quarkus appeared first on Piotr's TechBlog.

]]>
In this article, you will learn how to create a GraphQL application using the Quarkus framework. Our application will connect to a database, and we will use the Quarkus Panache module as the ORM provider. On the other hand, Quarkus GraphQL support is built on top of the SmallRye GraphQL library. We will discuss some more advanced GraphQL and JPA topics like dynamic filtering or relations fetching.

As an example, I will use the same application as in my previous article about Spring Boot GraphQL support. We will migrate it to Quarkus. Instead of the Netflix DGS library, we will use the already mentioned SmallRye GraphQL module. The next important challenge is to replace the ORM layer based on Spring Data with Quarkus Panache. If you would like to know more about GraphQL on Spring Boot read my article An Advanced GraphQL with Spring Boot and Netflix DGS.

Source Code

If you would like to try it by yourself, you may always take a look at my source code. In order to do that you need to clone my GitHub repository. After that go to the sample-app-graphql directory. Then you should just follow my instructions.

We use the same schema and entity model as in my previous article about Spring Boot and GraphQL. Our application exposes GraphQL API and connects to H2 in-memory database. There are three entities EmployeeDepartment and Organization – each of them stored in the separated table. Let’s take a look at a visualization of relations between them.

quarkus-graphql-entities

1. Dependencies for Quarkus GraphQL

Let’s start with dependencies. We need to include SmallRye GraphQL, Quarkus Panache, and the io.quarkus:quarkus-jdbc-h2 artifact for running an in-memory database with our application. In order to generate getters and setters, we can include the Lombok library. However, we can also take an advantage of the Quarkus auto-generation support. After extending entity class with PanacheEntityBase Quarkus will also generate getters and setters. We may even extend PanacheEntity to use the default id.

<dependencies>
   <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-smallrye-graphql</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-jdbc-h2</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-orm-panache</artifactId>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.16</version>
    </dependency>
</dependencies>

2. Domain Model for GraphQL and Hibernate

In short, Quarkus simplifies the creation of GraphQL APIs. We don’t have to manually define any schemas. The only thing we need to do is create a domain model and use some annotations. First things first – our domain model. To clarify, I’m using the same classes for ORM and API. Of course, we should create DTO objects to expose data as a GraphQL API, but I want to simplify our example implementation as much as I can. Here’s the Employee entity class.

@Entity
@Data
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Employee {
   @Id
   @GeneratedValue
   @EqualsAndHashCode.Include
   private Integer id;
   private String firstName;
   private String lastName;
   private String position;
   private int salary;
   private int age;
   @ManyToOne(fetch = FetchType.LAZY)
   private Department department;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;
}

Also, let’s take a look at the Department entity.

@Entity
@Data
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Department {
   @Id
   @GeneratedValue
   @EqualsAndHashCode.Include
   private Integer id;
   private String name;
   @OneToMany(mappedBy = "department")
   private Set<Employee> employees;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;
}

Besides entities, we also have input parameters used in the mutations. However, the input objects are much simpler than outputs. Just to compare, here’s the DepartmentInput class.

@Data
@NoArgsConstructor
public class DepartmentInput {
   private String name;
   private Integer organizationId;
}

3. GraphQL Filtering with Quarkus

In this section, we will create a dynamic filter in GraphQL API. Our sample filter allows defining criteria for three different Employee fields: salary, age and position. We may set a single field, two of them or all. Each condition is used with the AND relation to other conditions. The class with a filter implementation is visible below. It consists of several fields represented by the FilterField objects.

@Data
public class EmployeeFilter {
   private FilterField salary;
   private FilterField age;
   private FilterField position;
}

Then, let’s take a look at the FilterField implementation. It has two parameters: operator and value. Basing on the values of these parameters we are generating a JPA Criteria Predicate. I’m just generating the most common conditions for comparison between two numbers or strings.

@Data
public class FilterField {
   private String operator;
   private String value;

   public Predicate generateCriteria(CriteriaBuilder builder, Path field) {
      try {
         int v = Integer.parseInt(value);
         switch (operator) {
         case "lt": return builder.lt(field, v);
         case "le": return builder.le(field, v);
         case "gt": return builder.gt(field, v);
         case "ge": return builder.ge(field, v);
         case "eq": return builder.equal(field, v);
         }
      } catch (NumberFormatException e) {
         switch (operator) {
         case "endsWith": return builder.like(field, "%" + value);
         case "startsWith": return builder.like(field, value + "%");
         case "contains": return builder.like(field, "%" + value + "%");
         case "eq": return builder.equal(field, value);
         }
      }

      return null;
   }
}

After defining model classes we may proceed to the repository implementation. We will use PanacheRepository for that. The idea behind that is quite similar to the Spring Data Repositories. However, we don’t have anything similar to the Spring Data Specification interface which can be used to execute JPA criteria queries. Since we need to build a query basing on dynamic criteria, it would be helpful. Assuming that, we need to inject EntityManager into the repository class and use it directly to obtain JPA CriteriaBuilder. Finally, we are executing a query with criteria and returning a list of employees matching input conditions.

@ApplicationScoped
public class EmployeeRepository implements PanacheRepository<Employee> {

   private EntityManager em;

   public EmployeeRepository(EntityManager em) {
      this.em = em;
   }

   public List<Employee> findByCriteria(EmployeeFilter filter) {
      CriteriaBuilder builder = em.getCriteriaBuilder();
      CriteriaQuery<Employee> criteriaQuery = builder.createQuery(Employee.class);
      Root<Employee> root = criteriaQuery.from(Employee.class);
      Predicate predicate = null;
      if (filter.getSalary() != null)
         predicate = filter.getSalary().generateCriteria(builder, root.get("salary"));
      if (filter.getAge() != null)
         predicate = (predicate == null ?
            filter.getAge().generateCriteria(builder, root.get("age")) :
            builder.and(predicate, filter.getAge().generateCriteria(builder, root.get("age"))));
      if (filter.getPosition() != null)
         predicate = (predicate == null ? filter.getPosition().generateCriteria(builder, root.get("position")) :
            builder.and(predicate, filter.getPosition().generateCriteria(builder, root.get("position"))));

      if (predicate != null)
         criteriaQuery.where(predicate);

      return em.createQuery(criteriaQuery).getResultList();
   }

}

In the last step in this section, we are creating GraphQL resources. In short, all we need to do is to annotate the class with @GraphQLApi and methods with @Query or @Mutation. If you define any input parameter in the method you should annotate it with @Name. The class EmployeeFetcher is responsible just for defining queries. It uses built-in methods provided by PanacheRepository and our custom search method created inside the EmployeeRepository class.

@GraphQLApi
public class EmployeeFetcher {

   private EmployeeRepository repository;

   public EmployeeFetcher(EmployeeRepository repository){
      this.repository = repository;
   }

   @Query("employees")
   public List<Employee> findAll() {
      return repository.listAll();
   }

   @Query("employee")
   public Employee findById(@Name("id") Long id) {
      return repository.findById(id);
   }

   @Query("employeesWithFilter")
   public List<Employee> findWithFilter(@Name("filter") EmployeeFilter filter) {
      return repository.findByCriteria(filter);
   }

}

4. Fetching Relations with Quarkus GraphQL

As you probably figured out, all the JPA relations are configured in a lazy mode. To fetch them we should explicitly set such a request in our GraphQL query. For example, we may query all departments and fetch organization to each of the departments returned on the list. Let’s analyze the request visible below. It contains field organization related to the @ManyToOne relation between Department and Organization entities.

{
  departments {
    id
    name
    organization {
      id
      name
    }
  }
}

How to handle it on the server-side? Firstly, we need to detect the existence of such a relationship field in our GraphQL query. In order to analyze the input query, we can use DataFetchingEnvironment and DataFetchingFieldSelectionSet objects. Then we need to prepare different JPA queries depending on the parameters set in the GraphQL query. Once again, we will use JPA Criteria for that. The same as before, we place implementation responsible for performing a dynamic join inside the repository bean. Also, to obtain DataFetchingEnvironment we first need to inject GraphQL Context bean. With the following DepartmentRepository implementation, we are avoiding possible N+1 problem, and fetching only the required relation.

@ApplicationScoped
public class DepartmentRepository implements PanacheRepository<Department> {

   private EntityManager em;
   private Context context;

   public DepartmentRepository(EntityManager em, Context context) {
      this.em = em;
      this.context = context;
   }

   public List<Department> findAllByCriteria() {
      CriteriaBuilder builder = em.getCriteriaBuilder();
      CriteriaQuery<Department> criteriaQuery = builder.createQuery(Department.class);
      Root<Department> root = criteriaQuery.from(Department.class);
      DataFetchingEnvironment dfe = context.unwrap(DataFetchingEnvironment.class);
      DataFetchingFieldSelectionSet selectionSet = dfe.getSelectionSet();
      if (selectionSet.contains("employees")) {
         root.fetch("employees", JoinType.LEFT);
      }
      if (selectionSet.contains("organization")) {
         root.fetch("organization", JoinType.LEFT);
      }
      criteriaQuery.select(root).distinct(true);
      return em.createQuery(criteriaQuery).getResultList();
   }

   public Department findByIdWithCriteria(Long id) {
      CriteriaBuilder builder = em.getCriteriaBuilder();
      CriteriaQuery<Department> criteriaQuery = builder.createQuery(Department.class);
      Root<Department> root = criteriaQuery.from(Department.class);
      DataFetchingEnvironment dfe = context.unwrap(DataFetchingEnvironment.class);
      DataFetchingFieldSelectionSet selectionSet = dfe.getSelectionSet();
      if (selectionSet.contains("employees")) {
         root.fetch("employees", JoinType.LEFT);
      }
      if (selectionSet.contains("organization")) {
         root.fetch("organization", JoinType.LEFT);
      }
      criteriaQuery.where(builder.equal(root.get("id"), id));
      return em.createQuery(criteriaQuery).getSingleResult();
   }
}

Finally, we just need to create a resource controller. It uses our custom JPA queries defined in DepartmentRepository.

@GraphQLApi
public class DepartmentFetcher {

   private DepartmentRepository repository;

   DepartmentFetcher(DepartmentRepository repository) {
      this.repository = repository;
   }

   @Query("departments")
   public List<Department> findAll() {
      return repository.findAllByCriteria();
   }

   @Query("department")
   public Department findById(@Name("id") Long id) {
      return repository.findByIdWithCriteria(id);
   }

}

5. Handling GraphQL Mutations with Quarkus

In our sample application, we separate the implementation of queries from mutations. So, let’s take a look at the DepartmentMutation class. Instead of @Query we use @Mutation annotation on the method. We also use the DepartmentInput object as a mutation method parameter.

@GraphQLApi
public class DepartmentMutation {

   private DepartmentRepository departmentRepository;
   private OrganizationRepository organizationRepository;

   DepartmentMutation(DepartmentRepository departmentRepository, 
         OrganizationRepository organizationRepository) {
      this.departmentRepository = departmentRepository;
      this.organizationRepository = organizationRepository;
   }

   @Mutation("newDepartment")
   public Department newDepartment(@Name("input") DepartmentInput departmentInput) {
      Organization organization = organizationRepository
         .findById(departmentInput.getOrganizationId());
      Department department = new Department(null, departmentInput.getName(), null, organization);
      departmentRepository.persist(department);
      return department;
   }

}

6. Testing with GraphiQL

Once we finished the implementation we may build and start our Quarkus application using the following Maven command.

$ mvn package quarkus:dev

After startup, the application is available on 8080 port. We may also take a look at the list of included Quarkus modules.

quarkus-graphql-startup

Quarkus automatically generates a GraphQL schema based on the source code. In order to display it, you should invoke the URL http://localhost:8080/graphql/schema.graphql. Of course, it is an optional step. But something that will be pretty useful for us is the GraphiQL tool. It is embedded into the Quarkus application. It allows us to easily interact with GraphQL APIs and can be accessed from http://localhost:8080/graphql-ui/. First, let’s run the following query that tests a filtering feature.

{
  employeesWithFilter(filter: {
    salary: {
      operator: "gt"
      value: "19000"
    },
    age: {
      operator: "gt"
      value: "30"
    }
  }) {
    id
    firstName
    lastName
    position
  }
}

Here’s the SQL query generated by Hibernate for our GraphQL query.

select
   employee0_.id as id1_1_,
   employee0_.age as age2_1_,
   employee0_.department_id as departme7_1_,
   employee0_.firstName as firstnam3_1_,
   employee0_.lastName as lastname4_1_,
   employee0_.organization_id as organiza8_1_,
   employee0_.position as position5_1_,
   employee0_.salary as salary6_1_ 
from
   Employee employee0_ 
where
   employee0_.salary>19000 
   and employee0_.age>30

Our sample application inserts some test data to the H2 database on startup. So, we just need to execute a query using GraphiQL.

Now, let’s repeat the same exercise to test the join feature. Here’s our GraphQL input query responsible for fetching relation with the Organization entity.

{
  department(id: 5) {
    id
    name
    organization {
      id
      name
    }
  }
}

Hibernate generates the following SQL query for that.

select
   department0_.id as id1_0_0_,
   organizati1_.id as id1_2_1_,
   department0_.name as name2_0_0_,
   department0_.organization_id as organiza3_0_0_,
   organizati1_.name as name2_2_1_ 
from
   Department department0_ 
left outer join
   Organization organizati1_ 
      on department0_.organization_id=organizati1_.id 
where
   department0_.id=5

Once again, let’s view the response for our query using GraphiQL.

Final Thoughts

GraphQL support in Quarkus, like several other features, is based on the SmallRye project. In Spring Boot, we can use third-party libraries that provide GraphQL support. One of them is Netflix DGS. There is also a popular Kickstart GraphQL library described in this article. However, we can’t use any default implementation developed by the Spring Team.

With Quarkus GraphQL support we can easily migrate from Spring Boot to Quarkus. I would not say that currently, Quarkus offers many GraphQL features like for example Netflix DGS, but it is still under active development. We could also easily replace Spring Data with the Quarkus Panache project. The lack of some features similar to the Spring Data Specification, could be easily bypassed by using JPA CriteriaBuilder and EntityManager directly. Finally, I really like the Quarkus GraphQL support, because I don’t have to take care of the GraphQL schema creation, which is generated automatically basing on the source code and annotations.

The post An Advanced GraphQL with Quarkus appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2021/04/14/advanced-graphql-with-quarkus/feed/ 8 9672