Для реалізації 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, щоб ще більше прискорити обробку даних. Побачимось на наступній темі!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ