تطبيق التجميع والتخزين المؤقت
ادمج DataLoaders في محلّلات Spring Boot لتحسين استرداد البيانات وتقليل عمليات الوصول المتكررة إلى قاعدة البيانات.
تطبيق التجميع والتخزين المؤقت درس مجاني في GraphQL APIs with Spring Boot على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في GraphQL APIs with Spring Boot، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة GraphQL APIs with Spring Boot 4 دروس في المجموع.
بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.
DataLoaders: Time to Implement!
Welcome back! In previous lessons, we learned about the N+1 problem and how DataLoaders provide an elegant solution through batching and caching.
Today, we'll get hands-on and integrate DataLoaders into a Spring Boot GraphQL application. We'll see how to define a BatchLoader, register it, and use it in our GraphQL resolvers.
Simulating a Data Service
First, let's set up a simple mock service that simulates fetching users from a database. This service will be called by our DataLoaders.
Notice the System.out.println, which will help us observe when the actual 'database' call happens, demonstrating batching later.
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
// A simple User data class
class User {
String id;
String name;
User(String id, String name) {
this.id = id; this.name = name;
}
public String getId() { return id; }
public String getName() { return name; }
}
// Mock service to fetch users (simulates DB call)
class MockUserService {
private final Map<String, User> users = Map.of(
"1", new User("1", "Alice"),
"2", new User("2", "Bob"),
"3", new User("3", "Charlie"),
"4", new User("4", "David")
);
public List<User> findAllByIds(List<String> ids) {
System.out.println("DB Call: Fetching users for IDs: " + ids);
return ids.stream()
.map(users::get)
.filter(user -> user != null)
.collect(Collectors.toList());
}
}
public class Main {
public static void main(String[] args) {
MockUserService service = new MockUserService();
List<User> foundUsers = service.findAllByIds(List.of("1", "3"));
System.out.println("Found: " + foundUsers.size() + " users.");
}
}The BatchLoader Interface
The core of DataLoader's batching mechanism is the BatchLoader interface. It defines a single method: load(List<K> keys).
- It takes a list of keys (e.g., user IDs).
- It returns a
CompletionStage(a future-like object) of a list of values (e.g., users). - Crucially, this method is called once for all keys requested in a short window, allowing you to fetch them in a single optimized operation (like a single database query).
Implementing Your BatchLoader
Now, let's create our UserBatchLoader. It will implement the BatchLoader<String, User> interface, meaning it takes a String (user ID) as a key and returns a User object.
It uses our MockUserService to perform the actual batch fetch.
import org.dataloader.BatchLoader;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
// (Assume User and MockUserService are defined elsewhere)
// For runnable example, they would be in the same file.
public class UserBatchLoader implements BatchLoader<String, User> {
private final MockUserService userService;
public UserBatchLoader(MockUserService userService) {
this.userService = userService;
}
@Override
public CompletionStage<List<User>> load(List<String> userIds) {
// This method is called once with all requested IDs.
// We delegate to our mock service to fetch them in one go.
return CompletableFuture.supplyAsync(
() -> userService.findAllByIds(userIds)
);
}
}
public class Main {
public static void main(String[] args) {
MockUserService userService = new MockUserService();
UserBatchLoader batchLoader = new UserBatchLoader(userService);
System.out.println("UserBatchLoader created!");
}
}The DataLoaderRegistry
In a Spring Boot GraphQL application, DataLoader instances are managed within a DataLoaderRegistry. This registry acts as a container for all DataLoaders available during a GraphQL request execution.
- Each DataLoader is registered with a unique name (e.g., "userDataLoader").
- Resolvers retrieve the necessary DataLoader from this registry using its name.
- Spring for GraphQL typically handles the lifecycle and scope of this registry per request.
Setting up DataLoaders
Here's how you create a DataLoader instance from your BatchLoader and register it in a DataLoaderRegistry. This setup is crucial for making DataLoaders accessible to your resolvers.
import org.dataloader.DataLoader;
import org.dataloader.DataLoaderRegistry;
// (Assume User, MockUserService, UserBatchLoader are defined)
public class Main {
public static void main(String[] args) {
MockUserService userService = new MockUserService();
UserBatchLoader userBatchLoader = new UserBatchLoader(userService);
// Create a DataLoader instance
DataLoader<String, User> userDataLoader =
DataLoader.newDataLoader(userBatchLoader);
// Register it in a DataLoaderRegistry
DataLoaderRegistry registry = new DataLoaderRegistry();
registry.register("userDataLoader", userDataLoader);
System.out.println("DataLoader registered as 'userDataLoader'!");
// In a real app, this registry would be part of the GraphQLContext.
// Resolvers would then retrieve DataLoaders from it.
}
}Using DataLoader in Resolvers
Once your DataLoader is registered, you can use it within your GraphQL resolvers. Instead of directly calling your service, you'll call dataLoader.load(id).
- The
load()method returns aCompletionStage, not the direct object. - The DataLoader collects all
load()calls within the current execution frame before dispatching them to theBatchLoader. - Spring for GraphQL automatically dispatches the DataLoaders at the end of a data fetching cycle.
Resolver Integration in Action
This example simulates a GraphQL query where multiple user IDs are requested. Watch the "DB Call" message in the output to see how DataLoaders batch these requests into a single operation, even if they appear separate in the resolver logic.
import org.dataloader.DataLoader;
import org.dataloader.DataLoaderRegistry;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;
// User, MockUserService, UserBatchLoader definitions (from previous scenes)
class User {
String id; String name;
User(String id, String name) { this.id = id; this.name = name; }
public String getId() { return id; }
public String getName() { return name; }
}
class MockUserService {
private final Map<String, User> users = Map.of(
"1", new User("1", "Alice"), "2", new User("2", "Bob"),
"3", new User("3", "Charlie"), "4", new User("4", "David")
);
public List<User> findAllByIds(List<String> ids) {
System.out.println("DB Call: Fetching users for IDs: " + ids);
return ids.stream().map(users::get)
.filter(user -> user != null).collect(Collectors.toList());
}
}
class UserBatchLoader implements BatchLoader<String, User> {
private final MockUserService userService;
public UserBatchLoader(MockUserService userService) { this.userService = userService; }
@Override
public CompletionStage<List<User>> load(List<String> userIds) {
return CompletableFuture.supplyAsync(() -> userService.findAllByIds(userIds));
}
}
// Simulate a GraphQL Query Resolver method
public class UserResolver {
private final DataLoaderRegistry dataLoaderRegistry;
public UserResolver(DataLoaderRegistry registry) {
this.dataLoaderRegistry = registry;
}
public CompletionStage<User> getUserById(String id) {
DataLoader<String, User> userDataLoader =
dataLoaderRegistry.getDataLoader("userDataLoader");
return userDataLoader.load(id);
}
public static void main(String[] args) {
MockUserService userService = new MockUserService();
UserBatchLoader userBatchLoader = new UserBatchLoader(userService);
DataLoader<String, User> userDataLoader =
DataLoader.newDataLoader(userBatchLoader);
DataLoaderRegistry registry = new DataLoaderRegistry();
registry.register("userDataLoader", userDataLoader);
UserResolver resolver = new UserResolver(registry);
System.out.println("Requesting User 1 and User 2 concurrently...");
// Simulate two concurrent GraphQL field fetches
CompletableFuture<User> user1Future =
(CompletableFuture<User>) resolver.getUserById("1");
CompletableFuture<User> user2Future =
(CompletableFuture<User>) resolver.getUserById("2");
CompletableFuture.allOf(user1Future, user2Future)
.thenRun(() -> {
try {
System.out.println("Fetched User 1: " + user1Future.join().getName());
System.out.println("Fetched User 2: " + user2Future.join().getName());
} catch (Exception e) {
e.printStackTrace();
}
// For this manual demo, we dispatch the DataLoader.
// In Spring GraphQL, this is handled automatically.
userDataLoader.dispatch();
}).join();
}
}DataLoader's Built-in Caching
Beyond batching, DataLoaders also provide request-scoped caching out-of-the-box. This means:
- If
dataLoader.load("1")is called multiple times within the same GraphQL request, theBatchLoader's fetch function for ID "1" will only be invoked once. - Subsequent calls for the same ID within that request will return the already fetched (cached) result.
- This prevents redundant database calls for the same data within a single GraphQL operation, further boosting performance.
Check Your Understanding
You've learned how to implement DataLoaders for batching and caching. Let's test your knowledge!
Recap: Batching & Caching
Great job! You've successfully learned how to implement DataLoaders in Spring Boot GraphQL.
- We created a
MockUserServiceto simulate data fetching. - We built a
UserBatchLoaderto handle batching multiple ID requests. - We saw how to initialize and register
DataLoaderinstances in aDataLoaderRegistry. - Finally, we integrated DataLoaders into a resolver, observing how requests are batched and how caching prevents redundant calls.
By using DataLoaders, you significantly optimize your GraphQL API's performance, especially when dealing with complex data graphs and nested objects.
الأسئلة الشائعة
هل درس «تطبيق التجميع والتخزين المؤقت» مجاني؟
نعم — نص درس «تطبيق التجميع والتخزين المؤقت» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة GraphQL APIs with Spring Boot، انتقل إلى CoddyKit PRO. تتضمن دورة GraphQL APIs with Spring Boot 4 دروس في المجموع.
ماذا ستتعلم في «تطبيق التجميع والتخزين المؤقت»؟
ادمج DataLoaders في محلّلات Spring Boot لتحسين استرداد البيانات وتقليل عمليات الوصول المتكررة إلى قاعدة البيانات. تتمرن على GraphQL APIs with Spring Boot مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ GraphQL APIs with Spring Boot؟
لا تُشترط خبرة سابقة. GraphQL APIs with Spring Boot على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «تطبيق التجميع والتخزين المؤقت»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس GraphQL APIs with Spring Boot هذا؟
نعم. كل درس في GraphQL APIs with Spring Boot يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- شرح مشكلة N+1
- مقدمة إلى GraphQL DataLoaders
- تطبيق التجميع والتخزين المؤقت
- DataLoaders مع سياق Spring والعمليات غير المتزامنة