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:
@@ -29,4 +29,13 @@ public interface JourneyItemRepository extends JpaRepository<JourneyItem, UUID>
|
||||
|
||||
/** COUNT for the 100-item cap check — COUNT(*)-based, never MAX(position)-derived. */
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ public class JourneyItemService {
|
||||
}
|
||||
|
||||
public List<JourneyItemView> getItems(UUID geschichteId) {
|
||||
return journeyItemRepository.findByGeschichteIdOrderByPosition(geschichteId)
|
||||
return journeyItemRepository.findByGeschichteIdWithDocument(geschichteId)
|
||||
.stream().map(this::toView).toList();
|
||||
}
|
||||
|
||||
@@ -206,8 +206,8 @@ public class JourneyItemService {
|
||||
|
||||
JourneyItemView toView(JourneyItem item) {
|
||||
DocumentSummary docSummary = null;
|
||||
if (item.getDocumentId() != null) {
|
||||
Document doc = documentService.getSummaryById(item.getDocumentId());
|
||||
Document doc = item.getDocument();
|
||||
if (doc != null) {
|
||||
docSummary = toSummary(doc);
|
||||
}
|
||||
return new JourneyItemView(item.getId(), item.getPosition(), docSummary, item.getNote());
|
||||
|
||||
@@ -322,7 +322,6 @@ class JourneyItemServiceTest {
|
||||
Document doc = makeDoc(docId, null, List.of(), null, null);
|
||||
JourneyItem item = savedItemWithDoc(itemId, journey, 10, doc, "Old note");
|
||||
when(journeyItemRepository.findByIdAndGeschichteId(itemId, geschichteId)).thenReturn(Optional.of(item));
|
||||
when(documentService.getSummaryById(docId)).thenReturn(doc);
|
||||
JourneyItem saved = savedItemWithDoc(itemId, journey, 10, doc, null);
|
||||
when(journeyItemRepository.save(item)).thenReturn(saved);
|
||||
|
||||
@@ -372,7 +371,6 @@ class JourneyItemServiceTest {
|
||||
Document doc = makeDoc(docId, null, List.of(), null, null);
|
||||
JourneyItem item = savedItemWithDoc(itemId, journey, 10, doc, "Old");
|
||||
when(journeyItemRepository.findByIdAndGeschichteId(itemId, geschichteId)).thenReturn(Optional.of(item));
|
||||
when(documentService.getSummaryById(docId)).thenReturn(doc);
|
||||
JourneyItem saved = savedItemWithDoc(itemId, journey, 10, doc, null);
|
||||
when(journeyItemRepository.save(item)).thenReturn(saved);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user