Рекурсивный запрос JPA?

Есть ли у JPA 2 какой-либо механизм для запуска рекурсивных запросов?

Здесь моя ситуация: у меня есть сущность E, которая содержит целое поле x. Он также может иметь детей типа E, отображаемых через @OneToMany. То, что я хотел бы сделать, это найти E по первичному ключу и получить его значение x вместе со значениями x всех его потомков. Есть ли способ сделать это в одном запросе?

Я использую Hibernate 3.5.3, но я бы предпочел не иметь каких-либо явных зависимостей от API-интерфейсов Hibernate.


EDIT: в соответствии с this, Hibernate не имеет этой функции или, по крайней мере, в марте. Поэтому кажется маловероятным, что JPA получит его, но я хотел бы убедиться.

Ответ 1

Использование простой модели смежности, в которой каждая строка содержит ссылку на своих родителей, которая будет ссылаться на другую строку в той же таблице, плохо взаимодействует с JPA. Это связано с тем, что JPA не поддерживает генерацию запросов с использованием предложения Oracle CONNECT BY или стандартного оператора WITH SQL. Без какого-либо из этих двух пунктов невозможно сделать модель смежности полезной.

Однако есть несколько других подходов к моделированию этой проблемы, которые могут быть применены к этой проблеме. Первой является Модель Материализованного Пути. Здесь полный путь к узлу сведен в один столбец. Определение таблицы расширено так:

CREATE TABLE node (id INTEGER,
                   path VARCHAR, 
                   parent_id INTEGER REFERENCES node(id));

Вставка дерева узлов выглядит примерно так:

INSERT INTO node VALUES (1, '1', NULL);  -- Root Node
INSERT INTO node VALUES (2, '1.2', 1);   -- 1st Child of '1'
INSERT INTO node VALUES (3, '1.3', 1);   -- 2nd Child of '1'
INSERT INTO node VALUES (4, '1.3.4', 3); -- Child of '3'

Таким образом, чтобы получить узел '1' и все его дочерние элементы, запрос:

SELECT * FROM node WHERE id = 1 OR path LIKE '1.%';

Чтобы отобразить это в JPA, просто сделайте столбец 'path' атрибутом вашего постоянного объекта. Однако вам придется вести бухгалтерский учет, чтобы обновлять поле "путь". JPA/Hibernate не сделает это для вас. Например, если вы переместите узел на другого родителя, вам придется обновить родительскую ссылку и определить новое значение пути из нового родительского объекта.

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

Существует третий подход, который называется "Модель вложенного интервала", однако в нем заложено большое количество хранимых процедур для реализации.

Гораздо более полное объяснение этой проблемы описано в главе 7 "Искусства SQL".

Ответ 2

Лучший ответ в этом посте кажется огромным рутиной для меня. Мне уже приходилось иметь дело с моделями данных, где блестящие инженеры решили, что было бы хорошей идеей кодировать Tree Hiarchies в полях БД в виде текста, такого как: "Европа | Uk | Shop1 | John" и с массивными объемами данных в этих таблицах, Не удивительно, что выполнение запроса формы MyHackedTreeField LIKE "parentHierharchy%", где есть убийцы. Решение этой проблемы в конечном итоге потребовало создания в кэше памяти дерева hiearchies и многих других...

Если вам нужно запустить рекурсивный запрос, а объем данных не массивный... сделайте свою жизнь простой и просто загрузите поля БД, необходимые для выполнения вашего плана. И введите код рекурсии в java. Не делайте это в БД, если у вас нет веских оснований для этого.

И даже если объем данных у вас массивный, вы, скорее всего, можете разделить вашу проблему на самостоятельные рекурсивные партии деревьев и обрабатывать их по одному, не загружая сразу все данные.

Ответ 3

У меня была проблема, как это, запрос узлов меню из одной таблицы, То, как я основал, было так: Предположим, у нас есть класс с именем Node, который создал ассоциацию Unidirectional One-to-Many следующим образом:

    @OneToMany(  fetch = FetchType.EAGER)
    @JoinColumn(name = "parent_id", referencedColumnName = "id")
    private List<Node> subNodeList;

также имеет поле с именем, например, логическое isRoot в сущности, чтобы указать, является ли этот узел корневым элементом меню, и затем, запрашивая узлы, что isRoot имеет значение true, мы просто получаем верхние узлы и из-за FetchType.EAGER мы также получаем подузлы в List. Это вызовет несколько запросов, но для небольших меню, таких как вещи, все будет в порядке.