test(transcription): JOIN FETCH query loads all block mentions for propagation

Add findByPersonIdWithMentionsFetched to TranscriptionBlockRepository: subquery
finds blocks referencing the renamed person, outer JOIN FETCH loads their full
mentionedPersons collection. Avoids N+1 lazy selects in the propagation listener.
Filtered JOIN FETCH (WHERE m.personId=:personId) was rejected — it loads only one
mention entry per block, risking data loss on saveAllAndFlush.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-28 22:14:07 +02:00
parent 7906373053
commit 1f3f879f9c
2 changed files with 41 additions and 0 deletions

View File

@@ -31,6 +31,15 @@ public interface TranscriptionBlockRepository extends JpaRepository<Transcriptio
List<TranscriptionBlock> findByMentionedPersons_PersonId(UUID personId);
@Query("""
SELECT DISTINCT b FROM TranscriptionBlock b
JOIN FETCH b.mentionedPersons
WHERE b.id IN (
SELECT bb.id FROM TranscriptionBlock bb JOIN bb.mentionedPersons m WHERE m.personId = :personId
)
""")
List<TranscriptionBlock> findByPersonIdWithMentionsFetched(@Param("personId") UUID personId);
void deleteByAnnotationId(UUID annotationId);
int countByDocumentId(UUID documentId);

View File

@@ -92,4 +92,36 @@ class TranscriptionBlockMentionsRepositoryTest {
TranscriptionBlock reloaded = blockRepository.findById(saved.getId()).orElseThrow();
assertThat(reloaded.getMentionedPersons()).isEmpty();
}
@Test
void findByPersonIdWithMentionsFetched_returnsOnlyBlocksReferencingPerson_withMentionsLoaded() {
UUID augusteId = UUID.randomUUID();
UUID hermannId = UUID.randomUUID();
blockRepository.saveAndFlush(TranscriptionBlock.builder()
.annotationId(annotationId).documentId(documentId)
.text("Brief von @Auguste Raddatz an @Hermann Müller.")
.sortOrder(0)
.mentionedPersons(List.of(
new PersonMention(augusteId, "Auguste Raddatz"),
new PersonMention(hermannId, "Hermann Müller")))
.build());
blockRepository.saveAndFlush(TranscriptionBlock.builder()
.annotationId(annotationId).documentId(documentId)
.text("Unrelated block without Auguste.")
.sortOrder(1)
.mentionedPersons(List.of(new PersonMention(hermannId, "Hermann Müller")))
.build());
em.clear();
List<TranscriptionBlock> result =
blockRepository.findByPersonIdWithMentionsFetched(augusteId);
assertThat(result).hasSize(1);
assertThat(result.get(0).getMentionedPersons())
.extracting(PersonMention::getPersonId, PersonMention::getDisplayName)
.containsExactlyInAnyOrder(
org.assertj.core.groups.Tuple.tuple(augusteId, "Auguste Raddatz"),
org.assertj.core.groups.Tuple.tuple(hermannId, "Hermann Müller"));
}
}