# Hydrator Eager Loading System Das Eager Loading System löst das **N+1 Query Problem** durch intelligente Batch-Queries und bietet massive Performance-Verbesserungen für Relations-intensive Anwendungen. ## Problem: N+1 Query Problem ### Vorher (Lazy Loading) ```php // 1 Query für Users $users = $repository->findAll(); // SELECT * FROM users // N Queries für Relations (1 pro User) foreach($users as $user) { $user->posts; // SELECT * FROM posts WHERE user_id = ? $user->profile; // SELECT * FROM profiles WHERE user_id = ? } // Total: 1 + (N × 2) = 201 Queries für 100 Users! ``` ### Nachher (Eager Loading) ```php // 3 Queries total durch Batch-Loading $users = $hydrator->hydrateManyWithRelations($metadata, $userData, ['posts', 'profile']); // 1. SELECT * FROM users // 2. SELECT * FROM posts WHERE user_id IN (1,2,3,...,100) // 3. SELECT * FROM profiles WHERE user_id IN (1,2,3,...,100) // Total: 3 Queries für 100 Users = 98.5% Reduktion! ``` ## Features ### ✅ Selective Eager Loading ```php // Lade nur spezifizierte Relations $user = $hydrator->hydrateWithRelations($metadata, $data, ['posts', 'comments']); // Ohne Relations = normale Hydration $user = $hydrator->hydrateWithRelations($metadata, $data, []); ``` ### ✅ Batch Processing für Collections ```php // N+1 Problem gelöst für Collections $users = $hydrator->hydrateManyWithRelations($metadata, $dataRows, ['posts', 'profile']); // Intelligente Gruppierung nach Relation-Types // - belongsTo: IN-Queries für Foreign Keys // - hasMany: Gruppierung nach Local Keys // - one-to-one: Eindeutige Zuordnung ``` ### ✅ Relation-Type Support - **belongsTo**: Foreign Key Lookups mit IN-Queries - **hasMany**: Reverse Foreign Key Lookups mit Gruppierung - **one-to-one**: Eindeutige Relations-Zuordnung ## API Usage ### Single Entity mit Relations ```php $user = $hydrator->hydrateWithRelations( $userMetadata, $userData, ['posts', 'profile', 'roles'] ); // Relations sind sofort verfügbar, keine zusätzlichen Queries echo count($user->posts); // Kein Query echo $user->profile->bio; // Kein Query ``` ### Multiple Entities (Batch Loading) ```php $users = $hydrator->hydrateManyWithRelations( $userMetadata, $userDataRows, ['posts', 'comments', 'profile'] ); // Alle Relations wurden mit nur 4 Queries geladen: // 1x Users, 1x Posts, 1x Comments, 1x Profiles ``` ### Performance-kritische Scenarios ```php // Blog-System: Posts mit Comments, Tags, Categories $posts = $hydrator->hydrateManyWithRelations( $postMetadata, $postDataRows, ['comments', 'tags', 'category', 'author'] ); // Ohne Eager Loading: 1 + (50 × 4) = 201 Queries // Mit Eager Loading: 5 Queries = 97.5% Reduktion ``` ## Performance Benchmarks ### Real-World Scenarios | Scenario | Entities | Relations | Lazy Queries | Eager Queries | Reduction | |----------|----------|-----------|--------------|---------------|-----------| | User Dashboard | 50 users | posts, profile | 101 | 3 | **97.0%** | | Blog Listing | 20 posts | comments, tags, author | 81 | 4 | **95.1%** | | E-Commerce | 100 products | category, reviews, images | 301 | 4 | **98.7%** | | Social Feed | 30 posts | author, comments, likes | 91 | 4 | **95.6%** | ### Database Load Reduction ```php // Blog System Beispiel (50 Posts) $lazyQueries = 1 + (50 × 3); // 151 Queries $eagerQueries = 4; // 4 Queries $loadReduction = 151 / 4; // 37.75x weniger DB-Load! ``` ## Implementation Details ### Batch Loading Algorithm 1. **Entity Creation**: Erstelle alle Entities ohne Relations 2. **Key Collection**: Sammle alle IDs/Foreign Keys für Batch-Queries 3. **Batch Queries**: Ein Query pro Relation-Type mit IN-Clauses 4. **Grouping**: Gruppiere Related Entities nach Keys 5. **Assignment**: Weise Relations den entsprechenden Entities zu ### BelongsTo Relations (Foreign Key Lookup) ```php // Sammle alle Foreign Keys $foreignKeys = array_unique(array_column($dataRows, 'category_id')); // Ein Batch-Query für alle Categories $categories = $entityLoader->findBy(Category::class, ['id' => $foreignKeys]); // Gruppiere nach ID für schnelle Zuordnung $categoriesById = array_column($categories, null, 'id'); ``` ### HasMany Relations (Reverse Lookup) ```php // Sammle alle Entity IDs $userIds = array_column($userDataRows, 'id'); // Ein Batch-Query für alle Posts $posts = $entityLoader->findBy(Post::class, ['user_id' => $userIds]); // Gruppiere Posts nach user_id $postsByUserId = []; foreach($posts as $post) { $postsByUserId[$post->user_id][] = $post; } ``` ### One-to-One Relations ```php // Ähnlich wie hasMany, aber nur eine Relation pro Entity $profiles = $entityLoader->findBy(Profile::class, ['user_id' => $userIds]); $profileByUserId = array_column($profiles, null, 'user_id'); ``` ## Error Handling & Edge Cases ### Missing Relations ```php // Entities ohne Relations erhalten Default-Werte $userPosts = $postsByUserId[$userId] ?? []; // Empty array für hasMany $userProfile = $profilesByUserId[$userId] ?? null; // null für belongsTo/one-to-one ``` ### Empty Data Sets ```php $result = $hydrator->hydrateManyWithRelations($metadata, [], ['posts']); // Returns: [] (empty array, keine Fehler) ``` ### Invalid Relations ```php // Nicht-existierende oder Nicht-Relations werden übersprungen $user = $hydrator->hydrateWithRelations($metadata, $data, ['invalid_relation']); // Kein Fehler, Relation wird ignoriert ``` ## Best Practices ### 1. Selective Loading ```php // ✅ Gut: Nur benötigte Relations laden $posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author', 'comments']); // ❌ Schlecht: Alle Relations laden $posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author', 'comments', 'tags', 'category', 'ratings']); ``` ### 2. Batch Processing priorisieren ```php // ✅ Gut: Batch Loading für Collections $users = $hydrator->hydrateManyWithRelations($metadata, $dataRows, ['posts']); // ❌ Schlecht: Einzelne Hydration in Loop foreach($dataRows as $data) { $users[] = $hydrator->hydrateWithRelations($metadata, $data, ['posts']); } ``` ### 3. Relation Depth begrenzen ```php // ✅ Gut: Direkte Relations $posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author', 'comments']); // ⚠️ Consideration: Nested Relations // (Aktuell nicht unterstützt, würde zusätzliche Implementierung benötigen) $posts = $hydrator->hydrateManyWithRelations($metadata, $data, ['author.profile', 'comments.author']); ``` ## Benefits Summary 1. **Performance**: 95-98% Reduktion der Database-Queries 2. **Scalability**: Lineare statt exponentieller Query-Wachstum 3. **Resource Efficiency**: Drastisch reduzierte DB-Connection Usage 4. **User Experience**: Faster page loads and API responses 5. **Server Stability**: Reduzierter Memory- und CPU-Verbrauch 6. **Database Health**: Weniger Lock-Contention und Connection-Pool-Pressure ## Migration Path ### Bestehende Code-Base ```php // Vorher: Lazy Loading mit N+1 Problem $users = $repository->findAll(); foreach($users as $user) { // Implicit queries triggered $user->posts; $user->profile; } // Nachher: Explicit Eager Loading $userData = $repository->findAllAsData(); // Raw data query $users = $hydrator->hydrateManyWithRelations($metadata, $userData, ['posts', 'profile']); ``` ### Performance Monitoring - Monitor Query Count vor/nach Migration - Database Profiling für Query-Performance - Application Response Time Tracking - Memory Usage Monitoring bei Large Collections