Хозяин — барин. Как по мне, сила Хибернейта в state. Если вам stateful не нужно, то и JPA скорее всего не нужно. Делайте маппинги как душа пожелает.
Поделитесь, пожалуйста, как аудит делать будете?
Тут dou.ua/...to-use-hibernate/#1581886 спрашивал
Я
Получать entity из своего сервиса, обёрнутого транзакцией, и маппером конвертить?
Вы
В нее выходит ентити из сервиса,
Я
Вот и я так же — из своего фасада, который не знает о javax.jms.*
Вот и я так же [буду получать entity] из своего фасада, который не знает о javax.jms.*
Опасно тире ставить, лучше полными предложениями писать.
Уф!
Ваш вариант
class MyEntityServiceImpl implements MyEntityService { @Override @Transactional public MyEntity getBy(long id, DataIntegrity requiredIntegrity) { MyEntity e = dao.getBy(id); // null check bla bla bla if (requiredIntegrity.requiresFullyInitialized()) { dao.initialize(e); } } } class MyEntityRestController { @Autowired MyEntityToJsonMapper mapper; @Autowired MyEntityService service; getById(...) { MyEntity e = service.getBy(id, FULL_INTEGRITY); Json json = mapper.map(e); return json; } }
Мой вариант
class MyFacadeImpl implements MyFacade { @Override @Transactional public MyEntity getByIdForYou(long id) { MyEntity e = service.getBy(id); Hibernate.initialize(e); return e; } @Override @Transcational public SmallBO getByIdForMe(long id) { MyEntity e = service.getBy(id); smallBoMapper.toBo(e); } } // Controller is the same
В чём разница? Зачем выделять
Этот вызов — вне транзакции.
?
Я ваш код видел, о своём фасаде писал
— фасад (границы транзакции
Посмотрите на MyBatis, всяко легче будет, чем руками jdbc-маппинг писать.
Я упоминал о нём в статье.
А позвольте, я ещё так переформулирую.
Преобразованию во вью нечего делать в сервисном слое.
Суть ошибки, которую вы делаете с вашими фасадами, проста — вы формируете представление в слое сервисов, как бы вы не меняли терминологию.
Из какого источника вы взяли это утверждение?
Потому как я считаю, наоборот, ошибка не формировать представление в транзакции.
Более того, вы сами именно это и делаете.
MyEntity e = service.getBy(id, FULL_INTEGRITY);
Что вы сейчас вернули?
Persistence entity? Зачем же вы ей Hibernate.initialize() вызывали? Будете её модифицировать и flush-ить? Не похоже.
Вы вернули представление, потому как любой объект является представлением самого себя. Дальнейшее его использование — дергание геттеров — ничем принципиально не отличается от возврата аналогичного DTO с теми же полями.
Как вы их пишите, кстати? Руками в стрингах?
Ну да, а как их еще можно писать? Я не отношусь к тем, кто считает что орм должен полностью заменить написание sql.
Посмотрите на QueryDSL, должен подойти, получите compile-check.
Я так понимаю, чтобы ваш фасад умел отправлять javax.jms.TextMessage
Вы как будете TextMessage строить? Получать entity из своего сервиса, обёрнутого транзакцией, и маппером конвертить?
Вот и я так же — из своего фасада, который не знает о javax.jms.*
А чтобы вам воспользоваться моим сервисом
Я потерялся, в чём разница. У вас в сервисе транзакции, у меня в фасаде транзакции. Вы возвращаете entity
class MyEntityServiceImpl implements MyEntityService {
@Override
@Transactional
public MyEntity
Я из персистнутой entity возвращаю что угодно: для себя компактную дто с двумя графами из трёх, экономя лишние N+1, для вас — deep clone этой же самой энтити — или маппер-ом, или тем же самым Hibernate.initialize().
Кстати, оказывается ещё Hibernate.isInitialized() есть, наверное, чтобы на LLEs проверить.
статьи про @Entity Cat extends Pet вызывают у меня скепсис
Пытался, как мог, рассказать широкой аудитории, и преимущества, и подводные камни.
Мне видится, что основная беда, это бесконтрольное лечение LLEs eager-ом, а потом Хибернейт — гуано и тормозит, поэтому я вам оппонировал.
В остальных случаях зачем-то боретесь с LLEs
Мы с ними не боремся, потому что в нашем дизайне их просто не возникает
Либо у вас везде ManyToOne с eager-ом по умолчанию, как в коде примера, либо контрольный Hibernate.initialize в конце, который, по сути, то же самое. Либо просто по самому определению LLE вы можете обратиться к lazy-полю за рамками транзакции, забыв упомянуть его в fetch jpql.
не хватает критически важных для нормального ентерпрайза моментов.
Как например:
— когда был добавлен айтем в ордер
Думаете, я на языке общего назначания java не смогу удобно описать все эти вещи? :-)
Эта структура данных позволяет мне из коробки иметь весь тот функционал, о котором я написал в списке, ваша — нет.
Ну, так нечестно :-) Расширять ТЗ, а потом говорить, что мой код не соответствует.
Ладно, давайте по сути. Я что пытался донести в статье о JPA? Что можно строить доменную область, как удобно. Потом накидывать аннотаций, говорить персист — и хибернейт сам и DDL для схемы сгенерит, и данные туда положит-достанет. Да, у меня в Order-е одновременно и many-to-one (client) и one-to-many (item+quanity) за удобной мапой. А могут быть и ещё коллекции. Да, буду обязательные множители в N+1 собирать, когда представление в транзакции буду строить. Если совсем грустно, буду пытаться подсказать мега-джойн с помощью entityGraph-a, но это за рамками статьи.
Есть ограничения у этого подхода? Конечно есть! Может нагрузка такова, что не удобств будет.
А вы поправляете — реальность сурова, разобъётся. One-to-many нельзя, Влад опять же против. Не доменную область надо строить, а о будущих jpql-ях думать. Так чтобы не возникало
задачи получить из сущности, которая является графом с тремя узлами только два узла на выбор.
Мы интенсивно используем jpql,
а не просто сводно ходим по доменной модели?
Как вы их пишите, кстати? Руками в стрингах?
Следующий шаг на этом пути — инжект ентити менеджера прямо в контроллер и транзакция прямо на контроллере. Вы ведь тоже не видите проблем с таким подходом, да? :)
Нет, я избегаю
лишней загрузки, расширив границы тразакционности в слой представления,
Не контроллером единым, и из других точек бизнес-логика может вызываться. Но вы озвучьте, пожалуйста, проблемы этого подхода, второй раз предлагаете.
поскольку ваш сервис не возвращает мне данных, он возвращает сущность в неизвестном мне состоянии, что там загружено, или не загружено — никто не знает.
Если вы будете моим сервисом, а не фасадом, пользоваться, я специально для вас сделаю его safe-варинт: оберну АОП, транзакцией и пройдусь Hibernate.initialize() по возвращаемым entities, чтобы от LLE обезопасить.
Пожалуй, у меня больше нет аргументов. Боюсь, ситуация выглядит как с товарищем с ActiveRecord. Вы ему говорите — «Неправильно!», а он вам — «Удобно!» Наверное, все ваши усилия стоят
соблюдения независимости поведения компонентов от окружения и предсказуемости поведения.
Вот тут много вопросов. Мне неизвестно, как у вас связаны, Orders-Clients-Items.
Я о примере из статьи :-) Там и entities, и таблицы.
Пожалуйста, поправте свой комментарий относительно примера, и мы продолжим дискуссию. Как бы вы запрашивали
Orders+Clients (без товаров) или Orders+Items (без клиентов)
?
Симметрично. Впрочем, если вы укажете дополнительные материалы в пользу вашего подхода, я с интересом ознакомлюсь.
Даже если мы отказались от хибернейта, пройтись по лейзи-полям так сложно?
Ну, не знаю... За полями свои графы вообще-то. Ладно, неважно делать это Hibernate.initialize() или другим deep clone.
Вы серьезно считаете недостатком вызов 1 строки?
А почему вы так легко отмахиваетесь от загрузки (не факт, что нужной) всего графа?
Это как раз то, чего нету в вашем варианте. И вы так ничего на это и не ответили.
Давайте я переформулирую, а то может дто и фасад ввели в заблуждение.
Мои поинт абзаца «Entity и DTO» в том, что entities не должны фигурировать вне границ транзакции.
Можем назвать дто — бизнес-объектом, а фасад — внешним-сервисом, если это принципиально. С таким подходом нет LazyLoadingExceptions(LLEs), зато есть преимущества lazy-loading.
Вы так тоже делаете, только в части случаев, а я всегда.
В остальных случаях зачем-то боретесь с LLEs Hibernate.initialize-ом, который по сути тот же EAGER.
Да, ради соблюдения независимости поведения компонентов от окружения и предсказуемости поведения.
Что непредсказуемого в построенном dtovalue object?
Если мы отдаем сущность вовне персистентности, это значит, что все данные в этой сущности нужны для отображения, в 95% случаев. Обратный же кейс, когда часть данных не нужна, означает что с этой сущностью идет внутренняя работа в сервисном слое с логикой. Если у вас есть ситуации когда ни то и ни другое, и у вас реально есть необходимость маппить только половину графа в представление, я бы предположил проблемы в дизайне самих сущностей и самой модели предметной области.
Ой ли? Как по мне, вполне валидно запросить Orders+Clients (без товаров) или Orders+Items (без клиентов).
В других ветках вы утверждаете, что N+1 проблема неизбежена. Согласен. Но на мало-мальски сложном графе она превращается в N*M*K*X*Y*Z...+1 проблему. Hibernate.initialize-ом вы собираете всё произведение, а я предпочёл бы ограничиться необходимыми множителями ради производительности.
Опять же, вы сталкивались с этим, иначе не предлагали бы view-entities.
Для меня польза от этой ветки в том, что в следующей редакции я переформулирую абзац «Entity и DTOBO» и предостерегу от Hibernate.initialize().
Но в общем Хибернейт — гуано.
(голосом Подервянского) И яка розумная цьому stateful-альтернатива?
Там выше за stateless топят, а я нет — мне аудит нужен.
что становится сложнее отследить для частоиспользуемых сущностей, особенно если нет явных repository.save(), что и вызвало мою бурную реакцию.
что нету явного момента сохранения
А и их и не должно быть. Зачем делать save персиснтнутого объекта?
Вы в другой ветке ломаете копья, какой инструмент лучше, и вполне может быть, что для ваших задач stateless jooq, jdbc templates и т.д. предпочтительнее.
Можно хитрый batch update написать, пока глупый хибернейт будет сто раз селекты делать, а потом сто апдейтов за ними.
Я с задачей обновить версию parent-а при изменении child-а не сталкивался, поэтому, каюсь, вчера не вдумался. Зато вам спасибо — напомнили важное.
Абсолютно во всех проектах, когда все сервисы, дао и т.д. написаны, приходил бизнес и говорил: «А теперь надо сделать аудит. Какой пользователь, когда, что и на что поменял. В какую коллекцию что добавили, а что удалили. Складывайте это всё в отдельную табличку, мы будем на неё смотреть».
И тут внезапно я готов и принципом «доменная область должна сама всё уметь делать» поступиться, и state во благо, и селекты перед апдейтами нужны, и EventListener-ы — не костыли, а единственный выход. Несмотря на то, что в Hibernate одни нюансы, а в EclipseLink-е другие, успешно справлялся.
Поделитесь, пожалуйста, как бы вы со stateless репозиториями эту задачу решали.
Наворачивать руками dirty checking в джаве? Который уже сделан и отлажен в хибере?
Или триггерами в базе?
Потому как аудит бизнесу удобный нужен, не «orderId: 235, clientId: 111 -> 222», а понятный «Заказ 235, клиент: Петя -> Вася», плюс иногда понятность для бизнеса может отличаться от объектной, т.е. технически это изменение child-а, но записываем его как изменение parent-а.
Эээ... загрузить-то загрузит, вопрос каким способом. И с fetch можно N+1 получить, если там граф дальше.
Ваше приложение — ваши правила. Мне видятся два недостатка:
Я назубок не пам’ятаю всі комбінації jpql/hql/criteriaBuilder на fetchType/fetchStrategies. Кожен раз тре дивитись в лог. Тяжка спадщина наворотів слоїв.
в одному випадку треба половину графу витягнути, а в іншому
Це якось фреймоворку все одно вказати треба, може в ActiveRecord, дивлячись на пройдений хібером шлях, зробили очевидніше.
У хібері від @ManyToOne(EAGER) із-за сумісності, мабуть, відмовитись не можуть.
Я не впевнений, але думаю що пацани передбачили і на великих лістах конвертують то все в арраї.
Арраї не в усіх БД є, скоріше temporary table. Алгоритм для eager-а частини графа, гадаю, у всіх один:
І все-одно треба або дивитись за логами кожного запиту, бо після нової колекції може погіршитись, або плюнути і хай робить N+1, як йому треба.
Я згоден припустити, може в AR це позручніше, але сумніваюсь, что принципово інакше.
мутации сложно отслеживать в нетривиальных проектах.
Повторюсь, мы о сеттерах или бизнес методах-мутаторах?
Захотел в постгресе jsonb заиспользовать, чтобы ненужные джойны не плодить — добро пожаловать свой диалект писать.
А если ещё и от провайдера JPA абстрагироваться, то даже возможности писать диалект не будет.
Нужно обновить версию parent по обновлению child? Добро пожаловать в прекрасный мир Event Listeners.
А вот тут, при всём уважении к Владу, давайте с моей максимой зайдём.
JPA же предлагает другой принцип: вначале Java-классы, потом их сохранение в БД.
Видимо таки придётся во всех мутаторах child-а ручками дописать обновление версии parent-а. А то в EclipseLink лисенеры другие.
в моем мирке side-effects недолюбливают, особенно такие здоровые как флуш в базу графа сущности. Так что это как раз ИМХО не плюс, а здоровый минус Хибернейта.
Полагаю, есть разница между side-effects POJO и модификация объекта через бизнес-методы, как в моём примере? Flush — одна из основных фич Хибернейта, и если вы строго за иммутабельность, то он вам вряд ли нужен.
Это гипотетически или реальный кейс? Сложно представить развитый проект с файловым персистенсом.
Гипотетический. Я не застал, но возможно были джава-проекты без БД?
Ответил в другой ветке своими словами, тут приведу ссылочку dzone.com/...implementation-patterns-3
В данном контексте — держать состояние объектов
dou.ua/...to-use-hibernate/#1581886