Каскадные обновления с равенством бизнес-ключей: лучшие практики Hibernate? - PullRequest
1 голос
/ 17 июня 2009

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

Итак, у меня есть модель, включающая два класса с отношением один-ко-многим от родителя к ребенку. Каждый класс имеет суррогатный первичный ключ и составной бизнес-ключ с уникальными ограничениями.

<class name="Container">
    <id name="id" type="java.lang.Long">
        <generator class="identity"/>
    </id>
    <properties name="containerBusinessKey" unique="true" update="false">
        <property name="name" not-null="true"/>
        <property name="owner" not-null="true"/>
    </properties>
    <set name="items" inverse="true" cascade="all-delete-orphan">
        <key column="container" not-null="true"/>
        <one-to-many class="Item"/>
    </set>
</class>

<class name="Item">
    <id name="id" type="java.lang.Long">
        <generator class="identity"/>
    </id>
    <properties name="itemBusinessKey" unique="true" update="false">
        <property name="type" not-null="true"/>
        <property name="color" not-null="true"/>
    </properties>
    <many-to-one name="container" not-null="true" update="false"
                 class="Container"/>
</class>

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

Container c = new Container("Things", "Me");
c.addItem(new Item("String", "Blue"));
c.addItem(new Item("Wax", "Red"));

Transaction t = session.beginTransaction();
session.saveOrUpdate(c);
t.commit();

В первый раз все работает нормально, и Container и его Item сохраняются. Однако, если приведенный выше блок кода выполняется снова, Hibernate выбрасывает ConstraintViolationException - дубликаты значений для столбцов «имя» и «владелец». Поскольку новый экземпляр Container имеет нулевой идентификатор, Hibernate предполагает, что это несохраненный временный экземпляр. Это ожидается, но не желательно. Поскольку постоянные и временные объекты Container имеют одинаковые значения бизнес-ключей, мы действительно хотим выпустить обновление.

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

Container c = new Container("Things", "Me");
c.addItem(new Item("String", "Blue"));
c.addItem(new Item("Wax", "Red"));

Query query = session.createSQLQuery("SELECT id FROM Container" + 
                                     "WHERE name = ? AND owner = ?");
query.setString(0, c.getName());
query.setString(1, c.getOwner());
BigInteger id = (BigInteger)query.uniqueResult();
if (id != null) {
    c.setId(id.longValue());
}

Transaction t = session.beginTransaction();
session.saveOrUpdate(c);
t.commit();

Это почти удовлетворяет Hibernate, но поскольку отношение один-ко-многим от Container до Каскадов предметов, то же самое ConstraintViolationException также выбрасывается для дочерних Item объектов.

Мой вопрос: какова лучшая практика в этой ситуации? Настоятельно рекомендуется использовать суррогатные первичные ключи, а также рекомендуется использовать равенство бизнес-ключей. Однако, когда вы примените эти две рекомендации для совместной практики, два из величайших удобств Hibernate - saveOrUpdate и каскадные операции - кажутся почти бесполезными. На мой взгляд, у меня есть только два варианта:

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

Есть ли лучший способ? Я делаю совершенно неправильные предположения? Я надеюсь, что мне чего-то не хватает.

Спасибо.

Ответы [ 2 ]

1 голос
/ 17 июня 2009

Загрузите контейнер, прежде чем добавлять дочерние элементы (в любом случае, это хорошая идея), чтобы вы могли воспользоваться преимуществом поведения set. Когда вы добавляете Предмет в контейнер, он не будет добавлен.

О, вам также нужно убедиться, что вы переопределяете равенства и хэш-код в Item, чтобы все работало так, как вы ожидаете.

Итак ...

Query query = session.createSQLQuery("FROM Container " + 
                                     "WHERE name = ? AND owner = ?");
query.setString(0, "Things");
query.setString(1, "Me");
Container c = (BigInteger)query.uniqueResult();

c.addItem(new Item("String", "Blue"));
c.addItem(new Item("Wax", "Red"));

Transaction t = session.beginTransaction();
session.saveOrUpdate(c);
t.commit();
0 голосов
/ 17 июня 2009

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

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