Как работать с иерархическими данными в Java и SQL?

«Как работать с иерархическими данными в Java и SQL?» — вопрос из категории Базы данных, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Java иерархические структуры (деревья) обычно представляют с помощью рекурсивных классов.

class TreeNode<T> {
    T data;
    List<TreeNode<T>> children = new ArrayList<>();

    public TreeNode(T data) {
        this.data = data;
    }

    public void addChild(TreeNode<T> child) {
        children.add(child);
    }

    // Рекурсивный обход дерева (DFS)
    public void traverse() {
        System.out.println(data);
        for (TreeNode<T> child : children) {
            child.traverse();
        }
    }
}

// Использование
TreeNode<String> root = new TreeNode<>("Root");
root.addChild(new TreeNode<>("Child 1"));
root.traverse();

В SQL существует несколько паттернов для хранения иерархий:

  1. Adjacency List (Список смежности): Каждая запись хранит ссылку на родителя (parent_id). Для выборки поддерева требуются рекурсивные запросы (WITH RECURSIVE в PostgreSQL, CONNECT BY в Oracle).
  2. Nested Sets (Вложенные множества): Каждый узел имеет границы left и right. Позволяет быстро выбирать поддеревья без рекурсии, но сложен в модификации.
  3. Materialized Path (Материализованный путь): Путь от корня хранится в виде строки (например, 1.5.12). Легко читается и фильтруется по LIKE.

В JPA/Hibernate для Adjacency List используются аннотации @ManyToOne и @OneToMany.

@Entity
class Category {
    @Id
    private Long id;
    private String name;

    @ManyToOne
    @JoinColumn(name = "parent_id")
    private Category parent;

    @OneToMany(mappedBy = "parent")
    private List<Category> children;
}