JavaRush /Курсы /Модуль 5. Spring /Лекция 293: Практика: Реализация Batch Loading в Spring G...

Лекция 293: Практика: Реализация Batch Loading в Spring GraphQL

Модуль 5. Spring
30 уровень , 2 лекция
Открыта

Для реализации Batch Loading средствами Spring GraphQL мы будем использовать DataLoader. DataLoader помогает группировать загрузки данных (batch) и кэшировать их для последующего использования. Это особенно полезно, когда один запрос запрашивает много однотипных данных, которые можно извлечь за одну операцию, например, из базы данных.

Пример задачи: представьте, что у нас есть RESTful приложение для магазина книг. Каждый заказ связан с несколькими книгами, и нам нужно отображать заказы вместе с их книгами в GraphQL-запросе. Классическая реализация без Batch Loading может привести к проблеме N+1 запросов, где для каждой записи выполняется отдельный запрос к базе данных. Batch Loading поможет заменить это на единичный "оптовый" запрос.


Создание проекта с GraphQL и DataLoader

1. Настройка проекта

Убедитесь, что у вас уже есть настроенный Spring Boot проект с GraphQL. Если нет, добавьте следующие зависимости в 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>

Также добавьте необходимые зависимости для работы с базой данных (например, H2 или PostgreSQL) и JPA.

2. Создание сущностей

Создадим две сущности: Order (заказ) и Book (книга). Заказ будет содержать список идентификаторов книг, связанных с ним.


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

    private String customerName;

    @ElementCollection
    private List<Long> bookIds; // Идентификаторы книг, связанные с заказом

    // Геттеры и сеттеры
}


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

    private String title;
    private String author;

    // Геттеры и сеттеры
}

3. Репозитории

Создадим JPA-репозитории для взаимодействия с базой данных:


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

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

4. GraphQL Schema

Добавим схему GraphQL в файл schema.graphqls:


type Order {
    id: ID!
    customerName: String!
    books: [Book!]! # Ассоциированные книги
}

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

type Query {
    orders: [Order!]!
}

5. Resolver для запросов

Настраиваем резолвер для загрузки заказов. Однако, для загрузки книг мы будем использовать 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

Создадим DataLoader для загрузки книг по их идентификаторам.


@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. Интеграция DataLoader в GraphQL

Настроим обработчик, который подключит DataLoader к контексту GraphQL.


@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); // Регистрируем DataLoader
        return registry;
    }
}

8. Использование DataLoader в Fetcher'е

Пора связать заказы с их книгами, используя DataLoader в Data Fetcher:


@Component
public class OrderDataFetcher implements GraphQLResolver<Order> {

    public CompletableFuture<List<Book>> books(Order order, DataLoader<Long, Book> bookLoader) {
        // Загружаем книги для текущего заказа
        return bookLoader.loadMany(order.getBookIds());
    }
}

Тестирование Batch Loading

Создайте в GraphQL Playground или Postman запрос на получение заказов и связанных с ними книг:


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

Если всё настроено верно, запрос к базе данных произойдёт единожды для всех книг, а не для каждой отдельно.

Логирование SQL-запросов

Включите логирование SQL-запросов в application.properties:


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

Проанализируйте выполненные запросы: вы должны видеть один запрос для группы идентификаторов книг, а не множество отдельных.


Что дальше?

Мы реализовали Batch Loading с использованием DataLoader и интегрировали его в Spring GraphQL приложение. Теперь вы можете избежать проблемы N+1 запросов и повысить производительность ваших API. В следующих лекциях мы изучим асинхронные вызовы и научимся использовать DataLoader совместно с CompletableFuture, чтобы ускорить обработку данных ещё больше. До встречи на следующей теме!

Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Артём Уровень 112
30 октября 2025
Напишите, у кого получилось завести эту балалайку, и что вы для этого сделали!