@RomanMaras

Как эффективно использовать HQL при запросах сущностей с множеством связей?

Во время работы над учебным проектом получилась следующая схема в БД:
63c533ae65a17576328836.png
Всё как у людей: есть аккаунт, у него может быть несколько карт, а по каждой из карт есть входящие и исходящие транзакции.
Наверняка часто будет возникать ситуация, когда нужно для конкретного аккаунта (который мы определяем, например, по айди) выгрузить сразу все карты, а для каждой карты под шумок подгрузить входящие и исходящие транзакции. В проекте я использую Spring Data JPA, Но в данном случае я не могу просто запросить аккаунт с помощью метода JPA: вылетает hibernate.LazyInitializationException, а EagerLoad я использовать не хочу, вроде антипаттерн же

Что ж, попробуем написать ручками на HQL:
@Transactional
    public List<Card> findByOwnerId(int id) {
        List<Card> cards = entityManager
                .createQuery("select distinct c " +
                        "from Card c " +
                        "left join fetch c.inputTransactions " +
                        "where c.ownerAccount.id = :id", Card.class)
                .setParameter("id", id)
                .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false)
                .getResultList();
        cards = entityManager
                .createQuery("select distinct c " +
                        "from Card c " +
                        "left join fetch c.outputTransactions " +
                        "where c in :cards", Card.class)
                .setParameter("cards", cards)
                .setHint(QueryHints.HINT_PASS_DISTINCT_THROUGH, false)
                .getResultList();
        return cards;
    }


Тут всего два обращения к EntityManager: в первую очередь выгружаем все карты и сразу же джоиним на входящие транзакции, а вторым запросом подгружаем исходящие транзакции. Итак, при вызове метода мы вроде должны получить всего два запроса к БД, однако на практике (в логах Hibernate) я обнаружил целых пять (и это на случае, где у аккаунта всего одна карта и по ней есть три исходящие и две входящие транзакции): два из них – те самые корректные с джоином таблиц card и transaction , ещё два запроса – обращение к таблице account (без джоинов) и ещё один – странный запрос с джоином таблиц card и account по совпадению account_id.

Так вот, наконец, вопрос: я всё-таки делаю что-то не так, и при большом количестве карт и транзакций мой сервис захлебнется от переизбытка запросов, которые будет генерить Hibernate, либо я всё делаю верно и сдобно? Если разбираетесь, посоветуйте best practice для таких ситуаций
  • Вопрос задан
  • 165 просмотров
Пригласить эксперта
Ответы на вопрос 1
mayton2019
@mayton2019 Куратор тега Java
Bigdata Engineer
У меня - сходу замечание по твоему стилю. Зачем ты переменную card переписываешь? Это сбивает с толку.
List<Card> cards = .....

cards = entityManager

Нельзя одну переменную брать в двух ипостасях. Сэкономил в одном - проиграл в читаемости.

Второе я думаю что эта задача прекрасно решается одним SQL-запросом. Так было в продуктовых системах с 2000х годов когда еще не было этих ваших ORM/Hibernate. И все нормально работало. Поэтому делай все одним запросом. Не думай о накладных расходах в базе. Мой опыт показывает что база - лучше справляется когда выбирает все данные сразу одним курсором (запросом).

А игры с Lazy-Eager которые придумали в ORM решают проблемый самого ORM и ApplicationServer а базе они вобщем-то не нужны.

Если ты собрался глубоко заняться оптимизацией - посмотри лекцию Алименкова особенно в части трассировки Hibernate запросов. Собери цифры. Сколько карточек на 1 акк в среднем? 1 или 10 или 1000? Сколько транзакций на акк? Это все влияет на смыслы оптимизаций.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы