Зачем нужны два разных entity для документов
Что проверяют
Умеете ли вы обосновать проектное решение конкретной причиной, а не общими словами про SOLID. Вопрос часто задают по вашему тестовому заданию или проекту, поэтому сначала уточните контекст. Следом обычно спрашивают, почему не хватило одной сущности с ленивым полем.
Как отвечать
Назовите реальные причины разделения и механику JPA под ними:
- тяжёлое содержимое: файл или текст в
@Lobотдельно от метаданных. Списки и поиск работают по лёгкой сущностиDocument, аDocumentContentгрузится только при открытии.@Basic(fetch = FetchType.LAZY)на обычном поле Hibernate без bytecode enhancement не соблюдает, поэтому отдельная сущность с общим первичным ключом через@OneToOneи@MapsIdэто самый предсказуемый способ; - разный жизненный цикл: черновик правится, проведённый документ неизменен. Версии складывают в
DocumentVersionтолько на добавление, а вDocumentостаётся ссылка на актуальную; - шапка и строки: заголовок документа и табличная часть это разные таблицы, связанные
@OneToMany; - разные типы документов: у счёта и акта свои поля. Выбирают между наследованием (
SINGLE_TABLE,JOINED,TABLE_PER_CLASS), общим@MappedSuperclassи независимыми сущностями. Назовите цену каждого варианта: пустые колонки, лишниеJOIN, полиморфные запросы черезUNION.
Альтернатива: если нужно лишь не тащить содержимое в список, хватает проекции (select new ... в JPQL или интерфейсной проекции Spring Data) без второй сущности.
Если вопрос про entity и DTO, ответ другой: сущность отражает таблицу и управляется контекстом персистентности, а наружу в API уходит DTO, часто record. Сама сущность рекордом быть не может: JPA нужен конструктор без аргументов и не final-класс.
Где спотыкаются
Отвечают «по принципу единственной ответственности» и не называют ни одной практической выгоды.
Разделяют сущности, но делают двунаправленный @OneToOne с mappedBy на стороне Document. Эта сторона без enhancement не может быть ленивой, и содержимое всё равно подгружается, часто отдельным запросом на каждую строку списка.
Разделив, везде грузят обе сущности через JOIN FETCH. Выигрыш пропадает, а сложность остаётся.