CodeGym /Courses /Module 5. Spring /Lecture 293: Practice: Implementing Batch Loading in Spri...

Lecture 293: Practice: Implementing Batch Loading in Spring GraphQL

Module 5. Spring
Level 16 , Lesson 2
Available

To implement Batch Loading in Spring GraphQL we'll use DataLoader. DataLoader helps batch data fetching and cache results for subsequent use. This is especially useful when a single request needs many similar pieces of data that can be retrieved in one operation, for example from a database.

Problem example: imagine we have a RESTful app for a bookstore. Each order is linked to multiple books, and we need to display orders with their books in a GraphQL query. A classic implementation without Batch Loading can lead to the N+1 queries problem, where a separate DB query runs for each record. Batch Loading lets you replace that with a single bulk query.


Creating a project with GraphQL and DataLoader

1. Project setup

Make sure you already have a Spring Boot project with GraphQL. If not, add these dependencies to your pom.xml:


<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>12.0.0</version>
</dependency>
<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-java-tools</artifactId>
    <version>12.0.0</version>
</dependency>
<dependency>
    <groupId>com.graphql-java</groupId>
    <artifactId>java-dataloader</artifactId>
    <version>3.1.1</version>
</dependency>

Also add the DB dependencies you need (for example H2 or PostgreSQL) and JPA.

2. Creating entities

Let's create two entities: Order and Book. The order will hold a list of book IDs associated with it.


// Order.java
@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String customerName;

    @ElementCollection
    private List<Long> bookIds; // Identifiers of books associated with the order

    // Getters and setters
}


// Book.java
@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String author;

    // Getters and setters
}

3. Repositories

Create JPA repositories to interact with the database:


// OrderRepository.java
public interface OrderRepository extends JpaRepository<Order, Long> {
}

// BookRepository.java
public interface BookRepository extends JpaRepository<Book, Long> {
}

4. GraphQL Schema

Add the GraphQL schema to schema.graphqls:


type Order {
    id: ID!
    customerName: String!
    books: [Book!]! # Associated books
}

type Book {
    id: ID!
    title: String!
    author: String!
}

type Query {
    orders: [Order!]!
}

5. Resolver for queries

Set up a resolver to load orders. For loading books we'll use DataLoader.


@Component
public class OrderResolver implements GraphQLQueryResolver {

    private final OrderRepository orderRepository;

    public OrderResolver(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public List<Order> getOrders() {
        return orderRepository.findAll();
    }
}

6. DataLoader setup

Create a DataLoader to load books by their IDs.


@Component
public class BookDataLoader {

    private final BookRepository bookRepository;

    public BookDataLoader(BookRepository bookRepository) {
        this.bookRepository = bookRepository;
    }

    @Bean
    public DataLoader<Long, Book> bookLoader() {
        return DataLoader.newMappedDataLoader(bookIds -> {
            List<Book> books = bookRepository.findAllById(bookIds);
            return CompletableFuture.supplyAsync(() -> books.stream()
                    .collect(Collectors.toMap(Book::getId, book -> book)));
        });
    }
}

7. Integrating DataLoader into GraphQL

Set up a handler that attaches the DataLoader to the GraphQL context.


@Component
public class DataLoaderRegistryFactory {

    private final DataLoader<Long, Book> bookLoader;

    public DataLoaderRegistryFactory(DataLoader<Long, Book> bookLoader) {
        this.bookLoader = bookLoader;
    }

    @Bean
    public DataLoaderRegistry dataLoaderRegistry() {
        DataLoaderRegistry registry = new DataLoaderRegistry();
        registry.register("bookLoader", bookLoader); // Register the DataLoader
        return registry;
    }
}

8. Using DataLoader in the Fetcher

Now link orders to their books using DataLoader inside the Data Fetcher:


@Component
public class OrderDataFetcher implements GraphQLResolver<Order> {

    public CompletableFuture<List<Book>> books(Order order, DataLoader<Long, Book> bookLoader) {
        // Load books for the current order
        return bookLoader.loadMany(order.getBookIds());
    }
}

Testing Batch Loading

Create a query in GraphQL Playground or Postman to fetch orders and their associated books:


query {
    orders {
        id
        customerName
        books {
            id
            title
            author
        }
    }
}

If everything's set up correctly, the DB query for books will run once for all book IDs, not once per order.

Logging SQL queries

Enable SQL logging in application.properties:


spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

Analyze the executed queries: you should see a single query for the group of book IDs instead of many separate ones.


What's next?

We implemented Batch Loading with DataLoader and integrated it into a Spring GraphQL app. Now you can avoid the N+1 queries problem and boost your API performance. In the next lectures we'll cover async calls and how to use DataLoader together with CompletableFuture to speed up data processing even more. See you in the next lesson!

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION