Я новичок в 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 по умолчанию для отображения, соответствующего рекомендованной схеме.
Есть ли лучший способ? Я делаю совершенно неправильные предположения? Я надеюсь, что мне чего-то не хватает.
Спасибо.