fix(journeyitem): use JOIN FETCH to eliminate N+1 document queries

Add findByGeschichteIdWithDocument() to JourneyItemRepository with a
LEFT JOIN FETCH on document. getItems() now uses this query so that all
documents for a journey's items are loaded in a single SQL round-trip.
toView() now reads item.getDocument() directly from the already-fetched
association instead of issuing a separate documentService.getSummaryById()
call per item.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-08 20:46:54 +02:00
parent 164178ecf1
commit ad90ae75bf
3 changed files with 12 additions and 5 deletions

View File

@@ -29,4 +29,13 @@ public interface JourneyItemRepository extends JpaRepository<JourneyItem, UUID>
/** COUNT for the 100-item cap check — COUNT(*)-based, never MAX(position)-derived. */ /** COUNT for the 100-item cap check — COUNT(*)-based, never MAX(position)-derived. */
long countByGeschichteId(UUID geschichteId); long countByGeschichteId(UUID geschichteId);
/**
* Loads journey items with their linked Document in a single JOIN FETCH query,
* eliminating the N+1 SELECT that would occur when accessing item.getDocument()
* lazily for each item. Items without a document (note-only) are included via
* LEFT JOIN. Ordered by position ASC.
*/
@Query("SELECT ji FROM JourneyItem ji LEFT JOIN FETCH ji.document WHERE ji.geschichte.id = :geschichteId ORDER BY ji.position ASC")
List<JourneyItem> findByGeschichteIdWithDocument(@Param("geschichteId") UUID geschichteId);
} }

View File

@@ -183,7 +183,7 @@ public class JourneyItemService {
} }
public List<JourneyItemView> getItems(UUID geschichteId) { public List<JourneyItemView> getItems(UUID geschichteId) {
return journeyItemRepository.findByGeschichteIdOrderByPosition(geschichteId) return journeyItemRepository.findByGeschichteIdWithDocument(geschichteId)
.stream().map(this::toView).toList(); .stream().map(this::toView).toList();
} }
@@ -206,8 +206,8 @@ public class JourneyItemService {
JourneyItemView toView(JourneyItem item) { JourneyItemView toView(JourneyItem item) {
DocumentSummary docSummary = null; DocumentSummary docSummary = null;
if (item.getDocumentId() != null) { Document doc = item.getDocument();
Document doc = documentService.getSummaryById(item.getDocumentId()); if (doc != null) {
docSummary = toSummary(doc); docSummary = toSummary(doc);
} }
return new JourneyItemView(item.getId(), item.getPosition(), docSummary, item.getNote()); return new JourneyItemView(item.getId(), item.getPosition(), docSummary, item.getNote());

View File

@@ -322,7 +322,6 @@ class JourneyItemServiceTest {
Document doc = makeDoc(docId, null, List.of(), null, null); Document doc = makeDoc(docId, null, List.of(), null, null);
JourneyItem item = savedItemWithDoc(itemId, journey, 10, doc, "Old note"); JourneyItem item = savedItemWithDoc(itemId, journey, 10, doc, "Old note");
when(journeyItemRepository.findByIdAndGeschichteId(itemId, geschichteId)).thenReturn(Optional.of(item)); when(journeyItemRepository.findByIdAndGeschichteId(itemId, geschichteId)).thenReturn(Optional.of(item));
when(documentService.getSummaryById(docId)).thenReturn(doc);
JourneyItem saved = savedItemWithDoc(itemId, journey, 10, doc, null); JourneyItem saved = savedItemWithDoc(itemId, journey, 10, doc, null);
when(journeyItemRepository.save(item)).thenReturn(saved); when(journeyItemRepository.save(item)).thenReturn(saved);
@@ -372,7 +371,6 @@ class JourneyItemServiceTest {
Document doc = makeDoc(docId, null, List.of(), null, null); Document doc = makeDoc(docId, null, List.of(), null, null);
JourneyItem item = savedItemWithDoc(itemId, journey, 10, doc, "Old"); JourneyItem item = savedItemWithDoc(itemId, journey, 10, doc, "Old");
when(journeyItemRepository.findByIdAndGeschichteId(itemId, geschichteId)).thenReturn(Optional.of(item)); when(journeyItemRepository.findByIdAndGeschichteId(itemId, geschichteId)).thenReturn(Optional.of(item));
when(documentService.getSummaryById(docId)).thenReturn(doc);
JourneyItem saved = savedItemWithDoc(itemId, journey, 10, doc, null); JourneyItem saved = savedItemWithDoc(itemId, journey, 10, doc, null);
when(journeyItemRepository.save(item)).thenReturn(saved); when(journeyItemRepository.save(item)).thenReturn(saved);