Почитайте, плиз, про ООП и инкапсуляцию, подумайте, как сочетаются с ней билдер и сеттеры. К сожалению, это непопулярный взгляд, многие коллеги не видят никаких проблем в её нарушении, чай не проблема потом баги поправить, ещё и ещё.
А, кстати, я правильно предполагаю, что entityManager.createStoredProcedureQuery вне транзакции не работает? Т.е. границы транзакции всё таки были где-то "выше"?
Но транзакция не закрывалась, потому что наружу выходило checked exception EDeliveryException?
И проблема решилась не столько добавлением @Transactional к createInboxMessage методу — это какбэ принципиально не верно — задавать границы транзакции в слое репозитория, а потому что перестали checked exception бросать
Опять же, с Lazy имеем кучу проблем, таких как n+1
Любая Lazy-загрузка -- это потенциальная N+1 проблема. Но одновременно и очевидный прирост производительности. `fetch = FetchType.EAGER` вы же над коллекциями не ставите, верно?
и работа за пределами транзакций
Я не вполне понимаю, что вы имеете в виду, хотя и догадываюсь. Как вы работаете с lazy-коллекциями "за пределами транзакций"? C lazy-скалярами всё то же самое.
пример для h2
БД значения не имеет, lazy-loading делает хибернейт.
без танцев с бубнами
Byte enhancement -- это валидный инструмент, а не танцы с бубном. Поскольку String -- final class, а не интерфейс, по-другому, увы, никак, хотя ещё ленивую загрузку LOB-a можно сымитировать с наследуемым классом (но там свои компромиссы), если byte enhancement почему-то смущает.
Гм, раз вы меня дважды упрекаете, что я невнимательно читаю статью, придётся нарушить правило "overquoting -- зло".
Все примеры проверялись на Oracle и Postgres. Писались только изменённый поля.
Конечно, в любом случае пишутся только изменённые поля. Вопрос в том, что происходит под капотом БД. В случае Postgres мы имеем Vacuum, подробнее https://rbranson.medium.com/10-things-i-hate-about-postgresql-20dbab8c2791 раздел "#4: MVCC Garbage Frequently Painful". Для Oracle это не так, поэтому я и вставил оговорку "скорее всего".
>Во-вторых, "дефолтный" update-запрос по всем полям кешируется, а динамические будут каждый раз парсится
Современные СУБД решают эту проблему
Это очень смелое утверждение. Можете, пожалуйста, привести аргументы/доказательства?
Вы же сами начинаете с
Hibernate генерирует операторы SQL для операций CRUD всех объектов. Эти инструкции SQL генерируются один раз и кэшируются в памяти для повышения производительности.
Разница в поведении будет заметна, конечно, только под нагрузкой.
>Если не записывать обновленный LOB -- тогда и читать его не надо
Странное утверждение.
Вы пишете
...операция обновления может стоить очень дорого... размер полей большой (например, LOB). Решить эту проблему поможет аннотация для сущности @DynamicUpdate.
Действительно, если в сущности есть LOB, он будет а) вычитываться из БД всегда, б) попадать в дефолтный update, даже если не изменился. Это не оптимально. Вы предлагаете @DynamicUpdate , а я предлагаю сделать его LAZY . Для этого не обязательно выносить его в отдельную таблицу.
>Отслеживание изменений полей -- dirty checking -- происходит всегда
Я обратного и не утверждал.
Тем не менее, в статье есть
Или накладные расходы на отслеживание, или на запрос, содержащий все столбцы.
Накладные расходы на отслеживание -- dirty checking -- происходит всегда*, @DynamicUpdate на них не влияет.
*всегда -- имеется в виду а) транзакция не read-only, б) сессия не stateless
Я описал ситуации, когда данная технология будет уместна, равно как и проблемы.
А я пишу возражения к этими ситуациям, что, прежде чем браться за @DynamicUpdate:
если поле не обновляется вообще никогда, лучше его аннотировать @Column(updateable = false)
если в сущности LOB, есть смысл задуматься, не загружать ли его лениво
если в сущности много полей, а в разных бизнес-кейсах обновляются только некоторые из них, возможно, @DynamicUpdate будет оправдан. Осталось разобраться и померять, что такое "много" и "некоторые", и какой такой дизайн и кейсы получились.
я лично встречался с ситуациями, когда @DynamicUpdate резко повышал производительность
При всём уважении к вашему авторитету, ссылка на маленький тест демонстрирующий разницу между обычным обновлением и динамическим, была бы куда более весома. Пока я предполагаю, что таким образом (вместо lazy) боролись с LOB-ами.
Повторюсь,
без измерений под нагрузкой от @DynamicUpdateскорее всего будет псевдо-радость от "оптимизированных" SQL-запросов в логах и незначительная деградация производительности.
С in-clause вообще лучше быть осторожным. В постгресе вроде бы ограничений нет, а в оракле, например, по дефолту не более 1000 аргументов -- соответственно, надо разбивать на чанки и конкатенировать результат.
Если бизнес-логика позволяет (как в данном конкретном случае), имеет смысл предварить в репозитории
interface ArticleRepository extends CrudRepository<Article, UUID> {
default List<Article> findByPublisherId(List<UUID> ids) {
if (ids.isEmpty()) {
return Collections.emtpyList();
}
return _findByPublisherId(ids);
}
@Query("from Article where publisherId in :ids")
List<Article> _findByPublisherId(List<UUID> ids);
}
Если метод совсем безобразно могут вызывать, ещё и проверку на null добавить.
Во-первых, в MVCC базах скорее пишется новое состояние записи целиком, а не отдельные поля.
Во-вторых, "дефолтный" update-запрос по всем полям кешируется, а динамические будут каждый раз парсится -- правда, не уверен, на какой нагрузке это будет актульно.
Если не записывать обновленный LOB -- тогда и читать его не надо, либо вынести его в наследуемый класс, либо `@Basic(fetch = FetchType.LAZY)` c hibernate-enhance-maven-plugin.
Отслеживание изменений полей -- dirty checking -- происходит всегда, если транзакция не read-only, @DynamicUpdate тут не влияет.
@DynamicUpdate может быть актуален, если "дефолтный" update по всем полям зацепляет поле, которое а) не изменилось б) но в БД на него повешен триггер и происходит какой-то side effect.
В общем, без измерений под нагрузкой от @DynamicUpdate скорее всего будет псевдо-радость от "оптимизированных" SQL-запросов в логах и незначительная деградация производительности.
Почитайте, плиз, про ООП и инкапсуляцию, подумайте, как сочетаются с ней билдер и сеттеры. К сожалению, это непопулярный взгляд, многие коллеги не видят никаких проблем в её нарушении, чай не проблема потом баги поправить, ещё и ещё.
Чуток "причесал" вашу реализацию.
Но вообще лично я придерживаюсь подхода, который мне кажется более строгим:
entities не должны покидать границы транзакций
тогда и
equals()/hashCode()
переопредлять не нужноНу и посколько статья про Lombok:
как вы указываете,
toString()
можно только с явно указанными полями@Builder
считаю непременимым для Entity вообще, нужен явный конструктор безid
, чтобы гарантировать бизнес-целостностьк
@NoArgsConstructor
лучше добавитьaccess=AccessLevel.PROTECTED
, JPA достаточно, а разработчикам он не нужен по причине выше@AllArgsConstructor
скорее не нужен, потому как он включаетid
, а он обычно генерируетсяsequence
@Setter
тоже не нужен всё по той же причине необходимости бизнес-целостности@Data
отпадает, уже объяснено вышеИтого от Lombok-a остаются только `@ToString(onlyExplicitlyIncluded = true)` c явно указанными полями.
И
@Getter
. Геттеры нужны для а) конвертации entity в DTO, б) для тестов. В бизнес коде их быть не должно.Vlad Mihalchea предлагает способ, как писать
select new PostWithAuthorFlatDto
без указания полного имени класса.Крайне интересно. Поделитесь техническими подробностями, пожалуйста.
Можете показать код, воспроизводящий баг? Который в баг-репорт прикладывали?
И вот эти комментарии
и
... они в каких конкретно проектах/файлах/строчках?
Предполагаю, что вы ещё в контексте проблемы, раз
Вы можете подтвердить баг тестовым проектом, демонстрирующем проблему? Отправили баг-репорт в Spring?
Атрибут
transactionManager
в `@Transactional` конечно же использовали?А, кстати, я правильно предполагаю, что
entityManager.createStoredProcedureQuery
вне транзакции не работает? Т.е. границы транзакции всё таки были где-то "выше"?Но транзакция не закрывалась, потому что наружу выходило checked exception
EDeliveryException
?И проблема решилась не столько добавлением @Transactional к
createInboxMessage
методу — это какбэ принципиально не верно — задавать границы транзакции в слое репозитория, а потому что перестали checked exception бросать...?
Глушить эксепшены тоже, кстати, нехорошая практика.
В общем, вопросы есть к изложенному в статье.
Вопрос не по теме (по теме напишу отдельный комментарий) -- вы как новый счёт клиенту добавляете?
или
Это не так в случае lazy-скаляров. Когда они у вас заработают, посмотрите, как ведёт себя обновление.
Любая Lazy-загрузка -- это потенциальная N+1 проблема. Но одновременно и очевидный прирост производительности. `fetch = FetchType.EAGER` вы же над коллекциями не ставите, верно?
Я не вполне понимаю, что вы имеете в виду, хотя и догадываюсь. Как вы работаете с lazy-коллекциями "за пределами транзакций"? C lazy-скалярами всё то же самое.
БД значения не имеет, lazy-loading делает хибернейт.
Byte enhancement -- это валидный инструмент, а не танцы с бубном. Поскольку
String
-- final class, а не интерфейс, по-другому, увы, никак, хотя ещё ленивую загрузку LOB-a можно сымитировать с наследуемым классом (но там свои компромиссы), если byte enhancement почему-то смущает.Это пока нет) А внимательно перечитаете мой самый первый комментарий, погуглите -- уверен, заработает. Хинт: IDEA компилирует сама, byte enhancement не делает, поэтому запускайте тест напрямую с maven/gradle, вот "баг" на эту тему https://youtrack.jetbrains.com/issue/IDEA-159903/Hibernate-bytecode-instrumentation-code-is-being-overridden-by-IDEA
Гм, раз вы меня дважды упрекаете, что я невнимательно читаю статью, придётся нарушить правило "overquoting -- зло".
Конечно, в любом случае пишутся только изменённые поля. Вопрос в том, что происходит под капотом БД. В случае Postgres мы имеем Vacuum, подробнее https://rbranson.medium.com/10-things-i-hate-about-postgresql-20dbab8c2791 раздел "#4: MVCC Garbage Frequently Painful". Для Oracle это не так, поэтому я и вставил оговорку "скорее всего".
Это очень смелое утверждение. Можете, пожалуйста, привести аргументы/доказательства?
Вы же сами начинаете с
Мой поинт в том, что "дефолтный" update по всем полям был сделан так именно из кеширования. Более того, даже
in-clause
(если уж приходится им пользоваться) на разном количество параметров оптимизуют с помощьюhibernate.query.in_clause_parameter_padding
, подробнее https://vladmihalcea.com/improve-statement-caching-efficiency-in-clause-parameter-padding/Разница в поведении будет заметна, конечно, только под нагрузкой.
Вы пишете
Действительно, если в сущности есть LOB, он будет а) вычитываться из БД всегда, б) попадать в дефолтный update, даже если не изменился. Это не оптимально. Вы предлагаете
@DynamicUpdate
, а я предлагаю сделать егоLAZY
. Для этого не обязательно выносить его в отдельную таблицу.Тем не менее, в статье есть
Накладные расходы на отслеживание -- dirty checking -- происходит всегда*,
@DynamicUpdate
на них не влияет.*всегда -- имеется в виду а) транзакция не read-only, б) сессия не stateless
А я пишу возражения к этими ситуациям, что, прежде чем браться за
@DynamicUpdate
:если поле не обновляется вообще никогда, лучше его аннотировать
@Column(updateable = false)
если в сущности LOB, есть смысл задуматься, не загружать ли его лениво
если в сущности много полей, а в разных бизнес-кейсах обновляются только некоторые из них, возможно,
@DynamicUpdate
будет оправдан. Осталось разобраться и померять, что такое "много" и "некоторые", и какой такой дизайн и кейсы получились.При всём уважении к вашему авторитету, ссылка на маленький тест демонстрирующий разницу между обычным обновлением и динамическим, была бы куда более весома. Пока я предполагаю, что таким образом (вместо lazy) боролись с LOB-ами.
Повторюсь,
С
in-clause
вообще лучше быть осторожным. В постгресе вроде бы ограничений нет, а в оракле, например, по дефолту не более 1000 аргументов -- соответственно, надо разбивать на чанки и конкатенировать результат.Если бизнес-логика позволяет (как в данном конкретном случае), имеет смысл предварить в репозитории
Если метод совсем безобразно могут вызывать, ещё и проверку на
null
добавить.По поводу @DynamicUpdate -- спорно.
Во-первых, в MVCC базах скорее пишется новое состояние записи целиком, а не отдельные поля.
Во-вторых, "дефолтный" update-запрос по всем полям кешируется, а динамические будут каждый раз парсится -- правда, не уверен, на какой нагрузке это будет актульно.
Если не записывать обновленный LOB -- тогда и читать его не надо, либо вынести его в наследуемый класс, либо `@Basic(fetch = FetchType.LAZY)` c
hibernate-enhance-maven-plugin
.Отслеживание изменений полей -- dirty checking -- происходит всегда, если транзакция не read-only,
@DynamicUpdate
тут не влияет.@DynamicUpdate
может быть актуален, если "дефолтный" update по всем полям зацепляет поле, которое а) не изменилось б) но в БД на него повешен триггер и происходит какой-то side effect.В общем, без измерений под нагрузкой от
@DynamicUpdate
скорее всего будет псевдо-радость от "оптимизированных" SQL-запросов в логах и незначительная деградация производительности.