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

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

Модуль 5. Spring
Рівень 16 , Лекція 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 міститиме список ідентифікаторів книг, пов'язаних з ним.


// 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 схема

Додамо схему 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, щоб ще більше прискорити обробку даних. Побачимось на наступній темі!

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ