Эффективное представление Иерархий в Hibernate - PullRequest
5 голосов
/ 14 января 2010

У меня возникли проблемы с представлением иерархии объектов в Hibernate. Я искал вокруг, и не смог найти ни одного примера, делающего это или подобное - у вас есть мои извинения, если это общий вопрос.

У меня есть два типа, которые я бы хотел сохранить в Hibernate: группы и элементы.
* Группы идентифицируются уникальным образом по комбинации их имени и родителя.
* Группы расположены в несколько деревьев, так что каждая группа имеет ноль или одну родительскую группу.
* Каждый элемент может быть членом нуля или более групп.

В идеале, я бы хотел двунаправленных отношений, позволяющих мне получить:
* все группы, в которые входит элемент
* все предметы, которые являются членами определенной группы или ее потомков.
Мне также нужно иметь возможность просматривать дерево групп сверху, чтобы отобразить его в пользовательском интерфейсе.

В идеале базовая структура объекта будет выглядеть так:

class Group {
    ...
    /** @return all items in this group and its descendants */
    Set<Item> getAllItems() { ... }

    /** @return all direct children of this group */
    Set<Group> getChildren() { ... }
    ...
}

class Item {
    ...
    /** @return all groups that this Item is a direct member of */
    Set<Group> getGroups() { ... }  
    ...
}

Первоначально я только что установил простое двунаправленное отношение «многие ко многим» между Предметами и Группами, чтобы выборка всех элементов в иерархии групп требовала рекурсии вниз по дереву, а выборка групп для Предмета была простым средством получения. , т.е.:

class Group {
    ...
    private Set<Item> items;
    private Set<Group> children;
    ...
    /** @return all items in this group and its descendants */
    Set<Item> getAllItems() {
        Set<Item> allItems = new HashSet<Item>();
        allItems.addAll(this.items);
        for(Group child : this.getChildren()) {
            allItems.addAll(child.getAllItems());
        }
        return allItems;
    }

    /** @return all direct children of this group */
    Set<Group> getChildren() {
        return this.children;
    }
    ...
}

class Item {
    ...
    private Set<Group> groups;
    /** @return all groups that this Item is a direct member of */
    Set<Group> getGroups() {
        return this.groups;
    }
    ...
}

Однако это привело к нескольким запросам базы данных для выборки элементов в группе со многими потомками или для получения всего дерева групп для отображения в пользовательском интерфейсе. Это кажется очень неэффективным, особенно с более глубокими, большими групповыми деревьями. Есть ли лучший или стандартный способ представления этих отношений в Hibernate?

Я что-то делаю явно неправильно или глупо?

<Ч />

Моя единственная другая мысль до сих пор была такая: Замените поля id, parent и name группы уникальной строкой «path», которая указывает всю родословную группы, например ::
/ RootGroup
/ RootGroup / aChild
/ rootGroup / aChild / aGrandChild

Таблица объединения между группами и элементами будет содержать group_path и item_id.

Это сразу решает две проблемы, с которыми я страдал ранее:
1. Вся иерархия групп может быть извлечена из базы данных в одном запросе и восстановлена ​​в памяти.
2. Чтобы получить все элементы в группе или ее потомках, мы можем выбрать из group_item, где group_path = 'N' или group_path, например 'N /%'

Однако это, похоже, лишает смысла использование Hibernate. Все мысли приветствуются!

Ответы [ 4 ]

4 голосов
/ 14 января 2010

Я думаю, что реальный вопрос в том, где вы хотите, чтобы производительность снизилась. Если вы объявляете все свои отношения ленивыми и вытягиваете то, что вам нужно, то вы распределяете попадание со временем. Во многих случаях это означает, что ваш пользователь (человек или кремний) воспринимает более быстрое время реакции. Даже если вы обрабатываете весь граф одновременно, вам фактически не нужен весь граф в памяти одновременно.

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

Самое большое различие между ними - ваш конкретный вариант использования. Если это веб-сервер, загрузка всего графика в память приведет к уничтожению сервера. Большинство людей в любом случае не могут обрабатывать более 7-10 опций, и весь график может легко ошеломить пользователя выбором, затрудняющим навигацию по пользовательскому интерфейсу.

Также запомните эти правила оптимизации:

  1. Не
  2. Серьезно, не надо. Дешевле бросать оборудование на проблемы с производительностью, тогда оптимизировать код до такой степени, что он больше не будет обслуживаться.
  3. Если вы считаете, что должны, найдите узкое место 1 , используя инструмент профилирования, и исправьте его.
3 голосов
/ 15 января 2010

В моих прошлых проектах я реализовал два разных, но сопоставимых решения этой проблемы.

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

Используя эту стратегию, было довольно легко идентифицировать все корневые узлы (ОТ дерева t, где t.parent IS NULL), и было действительно легко получить полное дерево на основе корневого элемента (ОТ дерева t, ГДЕ t.root = : корень)

.

Отношение "Set children" было объявлено атрибутом @Transient. Используя простую служебную операцию, было довольно легко восстановить исходную древовидную структуру в любое время.

Другое решение касалось небольшого числа действительно больших древовидных структур. Но, к счастью, эти сооружения имели ограниченную глубину. Вместо того, чтобы использовать только один корень, я сопоставил несколько (6, если я правильно помню) уровней «родителей». Таким образом было легко получить и восстановить полные поддеревья из базы данных.

(Эти отношения от уровня 1 до уровня 6 были сопоставлены как отношения «ленивый многие-к-одному», и приложение сильно зависело от кэшей 2-го уровня.)

2 голосов
/ 14 мая 2010

Посмотрите на вложенные множества. Вот хорошая статья:

http://www.developersdex.com/gurus/articles/112.asp

По сути, вы жертвуете некоторой производительностью при изменении иерархии, чтобы упростить глубокий поиск, например, что вы хотите делать с элементами в качестве членов групп-потомков.

1 голос
/ 17 июля 2012

У меня такие же опасения по поводу высокой стоимости sql, вызванной рекурсивным алгоритмом при получении дочерних узлов. Я решил создать новую таблицу в базе данных, которая имеет отношение многие к одному с исходной таблицей, и вставить внешний ключ идентификатора узла. И следующий столбец - это идентификатор ребенка. С этой структурой я сохранил древовидную структуру один раз в таблице базы данных. Так что нет необходимости рекурсивно получать дочерние узлы. Я только что написал SQL-запрос, который объединяет две таблицы и дает мне дочерние элементы определенного узла.

...