Так ведь в этом случае каждое поле грузится в отдельной транзакции. Это хорошо лишь тогда, когда разработчик вообще не хочет думать об архитектуре и действительно бездумно открывает транзакции, или если нет вообще никаких претензий приложения на производительность (что, в общем то, не отменяет первого).
Плюсом (точнее, еще одним минусом) тут есть вероятность получить несогласованные данные даже на самых строгих уровнях изоляции, поскольку объекты достаются из разных транзакций.
Лучше планировать транзакции так, что бы все необходимые поля инициализировались в единственной транзакции.
Я боюсь, в моих примерах в принципе нет бизнес логики. Повторюсь, но в данной статье лишь указаны способы инициализации ленивых полей. Что бы это ни было, для того, что бы инициализировать ленивое поле, придется либо указывать @Transactional, либо создавать соответствующий метод в репозитории (или реализовывать более низкоуровневые варианты, это, конечно, тоже имеет место быть).
Если грязное чтение так критично, то, очевидно, нужно использовать блокировки и/или соответвтсвующий уровень изоляции....
Очевидно. Но это не отменяет написанного: длительные транзакции - зло. Мы можем избавиться от грязного чтения путем установки уровня изолированности (того же дефолтного в PostgreSQL READ_COMMITED), но появятся другие проблемы (например, невоспроизводимое чтение). Если мы поставим еще более высокий уровень изолированности - появятся проблемы с производительностью. Поэтому:
Лучше, когда длительных транзакций нет, чем когда они есть.
В статье я указал, что считаю вариант с точечным методом в случае работы с бд предпочтительнее.
Всё остальное зависит от конкретной ситуации.
Кэширование данных - тоже ничего с бизнес-логикой не имеет, этим занимаются инфраструктурные сервисы.
Каким образом отсутствие связи между кэшированием и бизнес-логикой коррелирует с тем, что определенные подходы становятся намного эффективнее, если применяется кэширование?
Если хорошо продумать, то требуемое количество окажется не так и велико, т.к. довольно редко в бизнесовых сценариях бывает так, что одна и та же сущность нужна в 100500 местах, да ещё каждый раз с разным набором. Как правило, какие-то данные будут требоваться всегда, и тогда их имеет смысл тянуть одним запросом.
Возможно. Но даже в случае трех различных наборов лично мне кажется удобнее использовать последний подход, а не клепать три разных метода загрузки. Но ответ на вопрос эффективности уже зависит от конкретного кейса.
P.S. Вообще если бы кто-то на заре spring-data не принял в угоду упрощения идиотское решение проаннотировать дефолтную реализацию JpaRepository @Transactional, то и никакой бы проблемы не было. Т.к. методы репозитория падали из-за отсутствия открытой транзакции, заставляя разработчика думать, а не тупо копировать строчки из step-by-step руководств.
И тогда все разработчики просто помечали бы все методы @Transactional)
А вся проблема в том, что надо правильно проектировать архитектуру. И не пренебрегать явным разделением инфраструктурных и бизнес-сервисов.
Это было указано лишь для того, что бы показать, что будет, если мы попытаемся использовать не инициализированное поле.
очевидно, не так. Т.к. в примерах налицо нарушение основополагающего принципа всей слоёной архитектуры: "Нельзя отдавать внешнему слою модель из внутреннего".
«Не так» - а как? Что то я не понял этой части комментария. Что вы предлагаете? Есть другие, более лучшие способы работы с ленивым полем?
Вообще непонятно в чём проблема с изоляцией транзакций, можете привести пример?
Чем длиннее транзакция, тем больше вероятность грязного чтения/записи данных. Если между загрузкой самого объекта и загрузкой ленивого поля (которых может быть несколько) будет некая пауза (которая может произойти даже в результате работы сборщика мусора), мы можем получить несогласованные данные при загрузке ленивых полей. Или надо более тонко управлять транзакциями, а не просто воткнуть аннотацию.
В результате код будет открывать как минимум 4 (четыре!!!) разные транзакции, что в конкуррентной среде приведёт к получению несогласованных данных.
Потому в статье и написано, что код «можно адаптировать» под работу с локальным хранилищем. Во-первых, такой код явно должен быть аннотирован @Transactional. Во-вторых – я не даром написал этот подход именно при хранении данных во внешнем источнике. При чем, наиболее реальный смысл он имеет только при использовании кэша – иначе, в большинстве случаев, «жирный» запрос будет выгоднее. Без кэша при работе с локальной бд всегда будет удобнее и выгоднее использовать уже существующие средства JPA (в т.ч. описанные в статье), чем подгружать поля отдельными запросами. Но если имеет место быть кэш, то в некоторых ситуациях можно подумать.
Ну и про точечные методы – я написал их проблему: количество.
Подводя итог, смысл статьи – не предложить везде использовать последний подход. Смысл статьи – показать способы работы с ленивыми полями. Даже указал в конце, что применять последний подход или нет – зависит от ситуации)
Если животное обрастает зависимостями, как в вашем примере - можно связать его [животное] с конкретной фабрикой, и тогда и не придется нагружать енам логикой, и избавимся от всех перечисленных в статье проблем в методе findFactory().
Естественно, расширять енам кучей полей/логики - плохая идея. Но в тех случаях, когда это оправдано - почему нет? Приведенный зоопарк - лишь наглядный пример. Также, я не согласен, что примеры нарушают SRP: енам имеет единственную причину для изменений - добавление нового животного.
Ну и, на самом деле, свичи из 14 джавы действительно уже хороши и уже являются не плохой альтернативой - я не считаю, что нужно везде все срочно переделывать на перечисления. Проблема в том, что далеко не все проекты используют джава 14, и некоторые кейсы с перечислениями точно будут выглядеть лучше)
Так ведь в этом случае каждое поле грузится в отдельной транзакции. Это хорошо лишь тогда, когда разработчик вообще не хочет думать об архитектуре и действительно бездумно открывает транзакции, или если нет вообще никаких претензий приложения на производительность (что, в общем то, не отменяет первого).
Плюсом (точнее, еще одним минусом) тут есть вероятность получить несогласованные данные даже на самых строгих уровнях изоляции, поскольку объекты достаются из разных транзакций.
Лучше планировать транзакции так, что бы все необходимые поля инициализировались в единственной транзакции.
Я боюсь, в моих примерах в принципе нет бизнес логики. Повторюсь, но в данной статье лишь указаны способы инициализации ленивых полей. Что бы это ни было, для того, что бы инициализировать ленивое поле, придется либо указывать @Transactional, либо создавать соответствующий метод в репозитории (или реализовывать более низкоуровневые варианты, это, конечно, тоже имеет место быть).
Очевидно. Но это не отменяет написанного: длительные транзакции - зло. Мы можем избавиться от грязного чтения путем установки уровня изолированности (того же дефолтного в PostgreSQL READ_COMMITED), но появятся другие проблемы (например, невоспроизводимое чтение). Если мы поставим еще более высокий уровень изолированности - появятся проблемы с производительностью. Поэтому:
Лучше, когда длительных транзакций нет, чем когда они есть.
В статье я указал, что считаю вариант с точечным методом в случае работы с бд предпочтительнее.
Всё остальное зависит от конкретной ситуации.
Каким образом отсутствие связи между кэшированием и бизнес-логикой коррелирует с тем, что определенные подходы становятся намного эффективнее, если применяется кэширование?
Возможно. Но даже в случае трех различных наборов лично мне кажется удобнее использовать последний подход, а не клепать три разных метода загрузки. Но ответ на вопрос эффективности уже зависит от конкретного кейса.
И тогда все разработчики просто помечали бы все методы @Transactional)
Это было указано лишь для того, что бы показать, что будет, если мы попытаемся использовать не инициализированное поле.
«Не так» - а как? Что то я не понял этой части комментария. Что вы предлагаете? Есть другие, более лучшие способы работы с ленивым полем?
Чем длиннее транзакция, тем больше вероятность грязного чтения/записи данных. Если между загрузкой самого объекта и загрузкой ленивого поля (которых может быть несколько) будет некая пауза (которая может произойти даже в результате работы сборщика мусора), мы можем получить несогласованные данные при загрузке ленивых полей. Или надо более тонко управлять транзакциями, а не просто воткнуть аннотацию.
Потому в статье и написано, что код «можно адаптировать» под работу с локальным хранилищем. Во-первых, такой код явно должен быть аннотирован @Transactional. Во-вторых – я не даром написал этот подход именно при хранении данных во внешнем источнике. При чем, наиболее реальный смысл он имеет только при использовании кэша – иначе, в большинстве случаев, «жирный» запрос будет выгоднее. Без кэша при работе с локальной бд всегда будет удобнее и выгоднее использовать уже существующие средства JPA (в т.ч. описанные в статье), чем подгружать поля отдельными запросами. Но если имеет место быть кэш, то в некоторых ситуациях можно подумать.
Ну и про точечные методы – я написал их проблему: количество.
Подводя итог, смысл статьи – не предложить везде использовать последний подход. Смысл статьи – показать способы работы с ленивыми полями. Даже указал в конце, что применять последний подход или нет – зависит от ситуации)
Если животное обрастает зависимостями, как в вашем примере - можно связать его [животное] с конкретной фабрикой, и тогда и не придется нагружать енам логикой, и избавимся от всех перечисленных в статье проблем в методе
findFactory().Естественно, расширять енам кучей полей/логики - плохая идея. Но в тех случаях, когда это оправдано - почему нет? Приведенный зоопарк - лишь наглядный пример. Также, я не согласен, что примеры нарушают SRP: енам имеет единственную причину для изменений - добавление нового животного.
Ну и, на самом деле, свичи из 14 джавы действительно уже хороши и уже являются не плохой альтернативой - я не считаю, что нужно везде все срочно переделывать на перечисления. Проблема в том, что далеко не все проекты используют джава 14, и некоторые кейсы с перечислениями точно будут выглядеть лучше)