Как смоделировать дерево с помощью Hibernate? - PullRequest
16 голосов
/ 15 августа 2011

У меня есть класс под названием «Домен». Каждый домен может иметь несколько поддоменов (одного типа).

Мне нужно иметь возможность определять поддомены и корневые домены. Субдомены могут иметь субдомены сами. Это может быть довольно много уровней.

Пример:

Rootdomain  
|- Subdomain 1  
|   |- Subdomain 2  
|   |
|   |- Subdomain 3
|
|- Subdomain 4
|   |- Subdomain 5

Как мне моделировать такой Java-класс с аннотациями Hibernate?

Ответы [ 4 ]

17 голосов
/ 15 августа 2011

Моделирование было бы довольно простым:

@Entity
class Domain {
  @ManyToOne //add column definitions as needed
  private Domain parent;      //each Domain with parent==null is a root domain, all others are subdomains

  @OneToMany //add column definitions as needed
  private List<Domain> subdomains;
}

Обратите внимание, что parent является свойством, отвечающим за запись в базе данных, т. Е. Вам необходимо установить parent для субдомена для отношения, которое будет сохранено.

Что не совсем тривиально, так это запросы, так как SQL (и, следовательно, HQL и JPQL) не легко поддерживает древовидные запросы. Hibernate может сделать это путем ленивой загрузки следующего уровня, но если вы хотите загрузить несколько уровней в одном запросе, вот где это становится сложным.

10 голосов
/ 15 августа 2011

Если вы хотите использовать отложенную инициализацию Hibernate / JPA (это нормальный случай), тогда вам не следует не использовать составной шаблон .

Проблема, связанная с Hibernate с Composite Pattern : в Composite у вас есть Composite, у которого есть ссылка на его дочерние Компоненты. Но Компонент является только абстрактным классом или Интерфейсом, поэтому каждый Компонент является Листом или Композитом. Если вы теперь используете ленивую инициализацию для набора составных объектов Composite, но затем некоторые, как необходимо привести конкретный дочерний элемент к Leaf или Composite, вы получите исключение Cast, потому что hibernate использует прокси для компонента, который не может быть приведен к Leaf или Composite. Композитный.

Второй недостаток составного шаблона заключается в том, что каждый класс будет листом или составным в течение своего полного жизненного цикла. Это хорошо, если ваша структура никогда не меняется. Но это не сработает, если лист должен стать составным, потому что кто-то хочет добавить подузел / лист.


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

@Entity
public class Domain {

    @Id
    private long id;

     /** The parent domain, can be null if this is the root domain. */
    @ManyToOne
    private Domain parent;

    /**
     * The children domain of this domain.
     * 
     * This is the inverse side of the parent relation.
     * 
     * <strong>It is the children responsibility to manage there parents children set!</strong>
     */
    @NotNull
    @OneToMany(mappedBy = "parent")
    private Set<Domain> children = new HashSet<Domain>();
    /**
     * Do not use this Constructor!
     * Used only by Hibernate.
     */
    Domain() {
    }

    /**
     * Instantiates a new domain.
     * The domain will be of the same state like the parent domain.
     *
     * @param parent the parent domain
     * @see Domain#createRoot()
     */
    public Domain(final Domain parent) {
        if(parent==null) throw new IllegalArgumentException("parent required");

        this.parent = parent;
        registerInParentsChilds();
    }

    /** Register this domain in the child list of its parent. */
    private void registerInParentsChilds() {
        this.parent.children.add(this);
    }

    /**
     * Return the <strong>unmodifiable</strong> children of this domain.
     * 
     * @return the child nodes.
     */
    public Set<Domain> getChildren() {
        return Collections.unmodifiableSet(this.children);
    }        

    /**
     * Move this domain to an new parent domain.
     *
     * @param newParent the new parent
     */
    public void move(final Domain newParent)  {
        Check.notNullArgument(newParent, "newParent");

        if (!isProperMoveTarget(newParent) /* detect circles... */ ) { 
            throw new IllegalArgumentException("move", "not a proper new parent", this);
        }

        this.parent.children.remove(this);
        this.parent = newParent;
        registerInParentsChilds();
    }

    /**
     * Creates the root.
     *
     * @param bid the bid
     * @return the domain
     */
    public static Domain createRoot() {
        return new Domain();
    }
}
2 голосов
/ 15 августа 2011

Я бы посоветовал вам сначала получить правильную модель OO только для Java, а затем позаботиться о том, какие аннотации Hibernate вы используете.Если вы обнаружите, что вам нужно законно изменить свою модель для Hibernate, вы всегда можете это сделать.

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

Использование языка реляционных баз данных (довольно непринужденно):

A.Если ваши домены (корневые домены и поддомены) имеют отношения (наборы из n-кортежей в таблице без дубликатов и с различимым первичным ключом) и

B.Ваши домены и субдомены имеют аналогичную структуру , , затем

C.Вы можете сохранить все из них в одной физической таблице, определив «родительский» внешний ключ, чтобы родительский FK одного кортежа отображался на первичный ключ другого.

Самое главное, эти рекурсивные отношения должны быть ациклическими.То, как вы это делаете, структурно зависит от вашего проблемного домена (у вас есть один корневой домен, или у вас может быть несколько несвязанных корневых доменов?) Корневой домен может быть удален с помощью родительского внешнего ключа NULL или с условиемгде родительский внешний ключ родительского кортежа равен его первичному ключу.У каждого есть свои плюсы и минусы (которые обычно являются предметом глупых пламенных войн).

1 голос
/ 15 августа 2011

Есть несколько возможностей, в зависимости от того, какие операции вам нужно выполнить.

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

Часто бывает полезно, чтобы все узлы дерева имели отношение многие-к-одному с корневым узлом. Это позволяет очень просто загрузить целое дерево за один запрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...