Для чего нужно ограничение final для класса в JPA
Что проверяют
Знаете ли вы, что спецификация JPA запрещает делать сущность final, и понимаете ли причину: прокси и ленивую загрузку. Вопрос часто звучит с подвохом, будто final что-то даёт. Следом обычно спрашивают про Kotlin, где классы финальные по умолчанию, или про record в роли сущности.
Как отвечать
Требования спецификации к классу сущности: он не final, у него нет final-методов и сохраняемых final-полей, и есть конструктор без аргументов с видимостью public или protected.
Причина в том, как провайдер вроде Hibernate реализует ленивость:
- прокси через наследование:
getReference()и связь@ManyToOne(fetch = LAZY)возвращают сгенерированный подкласс сущности (Hibernate строит его через ByteBuddy). Прокси переопределяет методы и при первом обращении идёт в базу. Отfinal-класса наследоваться нельзя,final-метод не переопределить; - конструктор без аргументов: провайдер создаёт пустой объект и потом заполняет поля из
ResultSet; - изменяемые поля: провайдер пишет в них при загрузке и
merge, а dirty checking сравнивает их со снимком.
Что будет при нарушении: для final-класса Hibernate не построит прокси, и ленивые связи на него начнут грузиться сразу. final-метод у прокси выполнится на неинициализированном объекте и вернёт null вместо данных. Ошибка тихая, поэтому её и любят спрашивать.
Для неизменяемых данных есть свои инструменты: record подходит для DTO-проекций в запросах и в Hibernate 6 для @Embeddable, а неизменяемость сущности выражается через @Immutable и отсутствие сеттеров. В Kotlin подключают плагины: kotlin-jpa генерирует конструктор без аргументов, allopen с аннотациями @Entity, @MappedSuperclass и @Embeddable снимает финальность.
Где спотыкаются
Отвечают, что final повышает безопасность и производительность сущности. Это перенос общих советов по Java туда, где спецификация прямо требует обратного.
Делают поля final и заполняют их конструктором, как в value-объекте. Провайдеру некуда записать значения при загрузке.
Забывают про Kotlin: без allopen сущности остаются финальными, и ленивая загрузка молча превращается в жадную.