Как правильно реализовать ассоциацию владелец-владелец-владелец2 в Java с Hibernate? - PullRequest
5 голосов
/ 04 сентября 2011

Я искал информацию о том, как реализовать следующую связь в спящем режиме, и, хотя руководство по спящему режиму очень подробное, я не нашел следующий адрес использования.

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

  • Контейнер связан с Содержанием через Положение.
  • Позиция не может существовать без Контейнера и Содержимого предмета.
    • Следовательно, если удаляется либо Контейнер, либо Содержащийся элемент, Позиция должна быть удалена.
  • Контейнер может содержать 0 или более позиций.
  • Позиция относится к одному и только одному Содержащемуся предмету.

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

Это отображение, которое у меня пока есть:

@Entity
public class Container
{
    @OneToMany(cascade = CascadeType.ALL,
               orphanRemoval = true,
               fetch = FetchType.EAGER)
    @JoinColumn(name = "container_fk")
    public Set<Position> getPositions() { return this.positions; }
    public void setPositions(final Set<Position> positions) { this.positions = positions; }
    private Set<Position> positions;
    ...
}

@Entity
public class Position
{
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "container_fk", insertable = false, updatable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    public Container getContainer() { return this.container; }
    public void setContainer(Container container) { this.container = container; }
    private Container container;

    @NaturalId
    @OneToOne(optional = false, fetch = FetchType.EAGER)
    @JoinColumn(name = "contained_fk")
    public Contained getContained() { return this.contained; }
    public void setContained(Contained contained) { this.contained = contained; }
    private Contained contained;

    // other attributes are owned by this relationship
    // (i.e., they don't make sense in either Container or Contained.
    ...
}

Чтобы удалить Позицию, при удалении элемента Contained в коде ContainedDao реализовано следующее (представлено без обработки исключений, а управление сеансом выполняется в базовом классе Dao для простоты):

@Repository
@Transactional(rollbackFor = Throwable.class)
public class ContainedDao extends TransactionalDao<Contained>
{
    public void delete(String id)
    {
        final Session session = getSession();

        // If there is a Position associated to the Contained item delete it,
        // and remove it from any Container collection.
        Position position = (Position) session.createCriteria(Position.class)
                                              .createCriteria("contained")
                                              .add(Restrictions.eq("id", id))
                                              .uniqueResult();
        if (position != null)
        {
            position.getContainer().getPositions().remove(position);
            session.delete(position);
        }

        // Delete the Contained item.
        Contained object = session.load(Contained.class, id);
        session.delete(contained);
    }
}

Я хотел бы как-то настроить аннотации так, чтобы метод ContainedDao.delete был упрощен до простого:

// Delete the Contained item.
Contained object = session.load(Contained.class, id);
session.delete(contained);

Возможно ли это? Или мое текущее решение - лучшее, что я могу получить? Есть ли лучший способ приблизиться к этому? Обратите внимание, что ключевым фактором здесь является то, что Position содержит дополнительные атрибуты; в противном случае, я бы настроил связь между Контейнером / Содержимым напрямую и позволил бы hibernate управлять ассоциацией.

1 Ответ

1 голос
/ 08 ноября 2011

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

Единственная разница между один-к-одному и отношения is-a находятся в жизненном цикле отношений.В отношении один к одному может содержаться экземпляр Contained, на который указывает Позиция.Отношение is-a более ограничено: отношение, по сути, является идентичностью самой Позиции, поэтому изменение ее недопустимо.

Для реализации is-a В связи с этим вам необходимы три вещи:

  1. Внешний ключ от Позиции к Содержанию также должен быть первичным ключом Позиции.Так как первичные ключи не меняются (я позволю себе роскошное предположение, что вы используете автоматически сгенерированный суррогатный ключ on Contained), отношение is-a также не может быть обновлено,Когда вы удаляете строку из Contained, вы также должны удалить все ссылочные позиции (их может быть не более одной).
  2. Класс Position должен расширять класс Contained.Как и выше для модели базы данных, объявление extends останется таковым на протяжении всего жизненного цикла объекта Position.Когда вы удаляете объект Contained, ваша позиция также исчезает (это один и тот же объект в памяти, доступ к которому осуществляется только через ссылки различных типов)
  3. В вашей конфигурации Hibernate укажите Position как присоединенный подкласс из Contained.

Тогда следующий код DAO для удаления экземпляра Contained также будет каскадно перемещаться в Position:

public void deleteContained(String id)
{
    Contained c = (Contained) session.createCriteria(Contained.class)
                                     .add(Restrictions.eq("id", id))
                                     .uniqueResult();

    // Removes associated Position too, if exists
    session.delete(c);
    // This would fail now:
    // session.load(Position.class, id);
}

Кроме того, удаление экземпляра Position приведет к каскаду в Contained:

public void deletePosition(String id)
{
    Position p = (Position) session.createCriteria(Position.class)
                                   .add(Restrictions.eq("id", id))
                                   .uniqueResult();

    // Removes associated Contained too
    session.delete(p);
    // This would fail now:
    // session.load(Contained.class, id);
}

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

...