Java Developer
  • Как использовать Hibernate: основные проблемы и их решения

    Я

    Получать entity из своего сервиса, обёрнутого транзакцией, и маппером конвертить?

    Вы

    В нее выходит ентити из сервиса,

    Я

    Вот и я так же — из своего фасада, который не знает о javax.jms.*

    Вот и я так же [буду получать entity] из своего фасада, который не знает о javax.jms.*

    Опасно тире ставить, лучше полными предложениями писать.

  • Как использовать Hibernate: основные проблемы и их решения

    Уф!

    Ваш вариант

    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
    

    В чём разница? Зачем выделять

    Этот вызов — вне транзакции.

    ?
    Я ваш код видел, о своём фасаде писал

    — фасад (границы транзакции
  • Как использовать Hibernate: основные проблемы и их решения

    Посмотрите на MyBatis, всяко легче будет, чем руками jdbc-маппинг писать.
    Я упоминал о нём в статье.

    Поддержал: Dmitry Bugay
  • Как использовать Hibernate: основные проблемы и их решения

    А позвольте, я ещё так переформулирую.

    Преобразованию во вью нечего делать в сервисном слое.
    Суть ошибки, которую вы делаете с вашими фасадами, проста — вы формируете представление в слое сервисов, как бы вы не меняли терминологию.

    Из какого источника вы взяли это утверждение?
    Потому как я считаю, наоборот, ошибка не формировать представление в транзакции.

    Более того, вы сами именно это и делаете.

    MyEntity e = service.getBy(id, FULL_INTEGRITY);

    Что вы сейчас вернули?
    Persistence entity? Зачем же вы ей Hibernate.initialize() вызывали? Будете её модифицировать и flush-ить? Не похоже.

    Вы вернули представление, потому как любой объект является представлением самого себя. Дальнейшее его использование — дергание геттеров — ничем принципиально не отличается от возврата аналогичного DTO с теми же полями.

  • Как использовать Hibernate: основные проблемы и их решения

    Как вы их пишите, кстати? Руками в стрингах?
    Ну да, а как их еще можно писать? Я не отношусь к тем, кто считает что орм должен полностью заменить написание 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-ом, а потом Хибернейт — гуано и тормозит, поэтому я вам оппонировал.

  • Как использовать Hibernate: основные проблемы и их решения

    В остальных случаях зачем-то боретесь с 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. Вы ему говорите — «Неправильно!», а он вам — «Удобно!» Наверное, все ваши усилия стоят

    соблюдения независимости поведения компонентов от окружения и предсказуемости поведения.
  • Как использовать Hibernate: основные проблемы и их решения

    Вот тут много вопросов. Мне неизвестно, как у вас связаны, Orders-Clients-Items.

    Я о примере из статьи :-) Там и entities, и таблицы.

    Пожалуйста, поправте свой комментарий относительно примера, и мы продолжим дискуссию. Как бы вы запрашивали

    Orders+Clients (без товаров) или Orders+Items (без клиентов)

    ?

  • Как использовать Hibernate: основные проблемы и их решения

    Симметрично. Впрочем, если вы укажете дополнительные материалы в пользу вашего подхода, я с интересом ознакомлюсь.

  • Как использовать Hibernate: основные проблемы и их решения

    Даже если мы отказались от хибернейта, пройтись по лейзи-полям так сложно?

    Ну, не знаю... За полями свои графы вообще-то. Ладно, неважно делать это 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().

  • Как использовать Hibernate: основные проблемы и их решения

    Но в общем Хибернейт — гуано.

    (голосом Подервянского) И яка розумная цьому stateful-альтернатива?

    Там выше за stateless топят, а я нет — мне аудит нужен.

  • Как использовать Hibernate: основные проблемы и их решения

    что становится сложнее отследить для частоиспользуемых сущностей, особенно если нет явных 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-а.

    Поддержал: Dmitry Bugay
  • Как использовать Hibernate: основные проблемы и их решения

    Эээ... загрузить-то загрузит, вопрос каким способом. И с fetch можно N+1 получить, если там граф дальше.

  • Как использовать Hibernate: основные проблемы и их решения

    Ваше приложение — ваши правила. Мне видятся два недостатка:

    • необходимость реализовывать инициализацию, либо привязкой к вендору Hibernante.initialize(e), либо руками
    • вы не гранулируете, если в сущности больше одного lazy графа, которые могут быть нужны/не нужны для разных сценариев, и вытягиваете их все в Dao.initialize(). Либо добавляете больше флажков в FixedDataIntegrity.
    Но, как я понимаю, вы сознательно идёте на это ради соблюдения концепции.
  • Как использовать Hibernate: основные проблемы и их решения

    Я назубок не пам’ятаю всі комбінації jpql/hql/criteriaBuilder на fetchType/fetchStrategies. Кожен раз тре дивитись в лог. Тяжка спадщина наворотів слоїв.

  • Как использовать Hibernate: основные проблемы и их решения

    в одному випадку треба половину графу витягнути, а в іншому

    Це якось фреймоворку все одно вказати треба, може в ActiveRecord, дивлячись на пройдений хібером шлях, зробили очевидніше.
    У хібері від @ManyToOne(EAGER) із-за сумісності, мабуть, відмовитись не можуть.

    Я не впевнений, але думаю що пацани передбачили і на великих лістах конвертують то все в арраї.

    Арраї не в усіх БД є, скоріше temporary table. Алгоритм для eager-а частини графа, гадаю, у всіх один:

    • якщо можна, робимо join
    • якщо ні — передаємо ліст айдішніків. IN() тут здивував через ліміт, temporary table більш надійніше, хоча, певне, ламається якщо DDL заборонені
    • оптимізації не вдались — N+1

    І все-одно треба або дивитись за логами кожного запиту, бо після нової колекції може погіршитись, або плюнути і хай робить N+1, як йому треба.

    Я згоден припустити, може в AR це позручніше, але сумніваюсь, что принципово інакше.

  • Как использовать Hibernate: основные проблемы и их решения

    мутации сложно отслеживать в нетривиальных проектах.

    Повторюсь, мы о сеттерах или бизнес методах-мутаторах?

    Захотел в постгресе jsonb заиспользовать, чтобы ненужные джойны не плодить — добро пожаловать свой диалект писать.

    А если ещё и от провайдера JPA абстрагироваться, то даже возможности писать диалект не будет.

    Нужно обновить версию parent по обновлению child? Добро пожаловать в прекрасный мир Event Listeners.

    А вот тут, при всём уважении к Владу, давайте с моей максимой зайдём.

    JPA же предлагает другой принцип: вначале Java-классы, потом их сохранение в БД.

    Видимо таки придётся во всех мутаторах child-а ручками дописать обновление версии parent-а. А то в EclipseLink лисенеры другие.

  • Как использовать Hibernate: основные проблемы и их решения

    в моем мирке side-effects недолюбливают, особенно такие здоровые как флуш в базу графа сущности. Так что это как раз ИМХО не плюс, а здоровый минус Хибернейта.

    Полагаю, есть разница между side-effects POJO и модификация объекта через бизнес-методы, как в моём примере? Flush — одна из основных фич Хибернейта, и если вы строго за иммутабельность, то он вам вряд ли нужен.

    Это гипотетически или реальный кейс? Сложно представить развитый проект с файловым персистенсом.

    Гипотетический. Я не застал, но возможно были джава-проекты без БД?

    Поддержал: Alex
  • Как использовать Hibernate: основные проблемы и их решения

    Ответил в другой ветке своими словами, тут приведу ссылочку dzone.com/...​implementation-patterns-3

  • Как использовать Hibernate: основные проблемы и их решения

    Разница между объектом (из доменной области) и репозиторием

    order.addItem(item1);

    и

    orderRepository.addItem(...)
    Я вижу то, что думаю? Обновление order без явного вызова репозитория или entityManager, расчитывая на autoflush ?

    Да, именно так.

    Я понимаю, конечно, что практика вносит свои стереотипы, и обычно либо приложение разрабатывается одновременно с БД, либо БД уже есть, да ещё с хранимыми процедурами, например, и нужно разработать приложение. Реализовывать можно разными инструментами, во втором случае для вызова ХП напрашивается, Spring JDBC Template, как вариант.

    JPA же о случае, когда доменная область уже сформирована и возникла необходимость хранить её в БД (а до этого, гипотетически, файлы устраивали). В этом случае достаточно описать маппинг, а Хибернейт и схему может создать, и данные туда положить, и изменения данных бизнес-логикой — проапдейтить.

    Что смущает в мутирующих объектах? О каких костылях Хибернейта идёт речь?

  • Как использовать Hibernate: основные проблемы и их решения

    Кажется, мы пришли к религиозному расхождению во взглядах.

    Я ратую за такой подход:

    • контроллер (отвечает, допустим, за REST);
    • фасад (границы транзакции и преобразование entities в dto);
    • сервис (бизнес-логика);
    • дао;

    Таким образом, в фасаде при построении дто, если есть обращение к lazy-полю, оно будет инициализировано запросами в БД, и нет необходимости заботиться о явной их загрузке.

    Вы же настаиваете, что ДТО должно строиться принципиально вне транзакции, хотя это требует ручной загрузки лейзи связей и диктует необходимость использования только одного сервиса.

← Сtrl 123456...12 Ctrl →