Hibernate - один из самых распространенных средств для использования модели ORM в java-приложениях. Но у него есть один существенный недостаток - довольно мало документации, где бы описывались именно тонкости применения Hibernate в тех или иных случаях. Я уж молчу про то, что на русском языке документации практически нет.
Данной публикацией я открываю серию статей о данной ORM системе. Я здесь не собираюсь цитировать книги про хибернейт, ORM и т.д., а буду делиться своими наблюдениями, результатами экспериментов и т.д. Дело в том, что по роду деятельности мне приходится чуть ли не каждый день с ним работать и кое-какой опыт имеется.
Очень важными для меня будут Ваши коментарии, указания на ошибки и неточности, вопросы и предложения.
Первая статья будет про такой интересный, многими используемый но не всегда понятный параметр как inverse.
Практически в любом приложении, использующем Hibernate возникает необходимость использования двунаправленых ассоциаций. Двунаправленная ассоциация очень удобна, когда обоим классам в ней учавствующим необходимо знать друг о друге. Например сущность-автопроизводитель содержит коллекцию моделей произведенных авто, а сущность-авто содержит поле "производитель". Рассмотрим двунаправленную асоциацию на примере отношения "many-to-one" и соответственно "one-to-many".
Пусть есть сущность "документ" (DMSDocument) и "рецензия на документ" (DMSReview). Соответственно документ имеет коллекцию рецензий, а рецензия - поле, указывающее к какому документу она относится.
Файл мэппинга:
Разберем логику добавления нового документа при inverse = "false".
Пусть за добавление документа отвечает следующий код:
При inverse=false у нас будут выполнены следующие SQL-запросы (посмотреть можно установив опцию hibernate.show_sql в true):
Вот отсюда начинается самое интересное - алгоритм добавления сущностей в БД.
1. Сначала сохраняется сущность DMSReview. Именно здесь нас подстерегает основная опастность: хорошо если соответствующая полю document запись в БД уже есть. А если записи нет, то в поле document будет записан NULL, но создавая файл-мэппинга мы указали ограничение not-null="true", соответсвенно при генерации DDL (Data Definition Language) по файлу мэппинга будет создано ограничение целостности (поле document не может принимать значение NULL), произойдет ошибка и выполнится откат транзакции.
2. Так как мы добавили следующий код:
то происходит обновление записи о документе. Хибернейт не отслеживает какие поля менялись и менялись ли вообще (в частности, приведенная выше строчка вообще не меняет ни одно поле в таблице tbl_dms_document). Он "видит", что объет изменился и просто делает его апдейт. Соответственно в случае, если объект DMSDocument не изменяется в рамках транзакции, то и данного запроса не будет.
3. Происходит изменение записи о рецензии. Ей наконец-то устанавливается нужное значение поля document - начинает работать ассоциация.
Теперь рассмотрим противоположную ситуацию: inverse="true".
Сначала рассмотрим, что происходит в данном случае. А происходит ни больше ни меньше как следующее:
Как видим ситуация изменилась - пропал последний апдейт! Дело в том, что ассоциация теперь как бы инвертирована - она управляется не со стороны DMSDocument, а со стороны DMSReview! Соответственно, DMSReview уже на этапе первого insert "знает" к какому документу она относится и поле document сразу заполнено корректно. Поэтому надобность в последнем апдейте отпадает.
Если же объект DMSDocument не существует, а создается вместе с коллекцией объектов DMSReview, то сначала в базу будет записан он, соответственно у него появится id и объекты DMSReview будут опять таки сохранены с корректным значением поля document, естественно равным значению поля id сущности DMSDocument. Нарушения целостности в случае inverse="true" не будет.
Понравилось сообщение - подпишись на блог через RSS
Данной публикацией я открываю серию статей о данной ORM системе. Я здесь не собираюсь цитировать книги про хибернейт, ORM и т.д., а буду делиться своими наблюдениями, результатами экспериментов и т.д. Дело в том, что по роду деятельности мне приходится чуть ли не каждый день с ним работать и кое-какой опыт имеется.
Очень важными для меня будут Ваши коментарии, указания на ошибки и неточности, вопросы и предложения.
Первая статья будет про такой интересный, многими используемый но не всегда понятный параметр как inverse.
Практически в любом приложении, использующем Hibernate возникает необходимость использования двунаправленых ассоциаций. Двунаправленная ассоциация очень удобна, когда обоим классам в ней учавствующим необходимо знать друг о друге. Например сущность-автопроизводитель содержит коллекцию моделей произведенных авто, а сущность-авто содержит поле "производитель". Рассмотрим двунаправленную асоциацию на примере отношения "many-to-one" и соответственно "one-to-many".
Пусть есть сущность "документ" (DMSDocument) и "рецензия на документ" (DMSReview). Соответственно документ имеет коллекцию рецензий, а рецензия - поле, указывающее к какому документу она относится.
Файл мэппинга:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE hibernate-mapping SYSTEM
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="ru.naumen.dms.bobjects.dmsdocument">
<class name="DMSDocument" table="TBL_DMS_DOCUMENT">
<key column="Document_UUID"
foreign-key="FK_DMSCoreBO_DMSDocument" />
<property name="htmlContent"
type="ru.naumen.core.util.BinaryBlobType" />
<set name="reviewSet" inverse="false">
<key column="document" />
<one-to-many class="ru.naumen.dms.bobjects.dmsdocument.DMSReview" />
</set>
</class>
<class name="DMSReview" table="TBL_DMS_REVIEW">
<key column="Review_UUID"
foreign-key="FK_DMSCoreBO_DMSReview" />
<property name="htmlContent"
type="ru.naumen.core.util.BinaryBlobType" />
<many-to-one name="document" column="document"
class="ru.naumen.dms.bobjects.dmsdocument.DMSDocument"
not-null="true" />
</class>
</hibernate-mapping>
<!DOCTYPE hibernate-mapping SYSTEM
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="ru.naumen.dms.bobjects.dmsdocument">
<class name="DMSDocument" table="TBL_DMS_DOCUMENT">
<key column="Document_UUID"
foreign-key="FK_DMSCoreBO_DMSDocument" />
<property name="htmlContent"
type="ru.naumen.core.util.BinaryBlobType" />
<set name="reviewSet" inverse="false">
<key column="document" />
<one-to-many class="ru.naumen.dms.bobjects.dmsdocument.DMSReview" />
</set>
</class>
<class name="DMSReview" table="TBL_DMS_REVIEW">
<key column="Review_UUID"
foreign-key="FK_DMSCoreBO_DMSReview" />
<property name="htmlContent"
type="ru.naumen.core.util.BinaryBlobType" />
<many-to-one name="document" column="document"
class="ru.naumen.dms.bobjects.dmsdocument.DMSDocument"
not-null="true" />
</class>
</hibernate-mapping>
Разберем логику добавления нового документа при inverse = "false".
Пусть за добавление документа отвечает следующий код:
// Загружаем документ из БД
DMSDocument document = (DMSDocument) PrefixObjectLoaderFacade.getObjectByUUID("coreboo2k04pg0000hd7rmt2hil696q4");
DMSReview review = new DMSReview();
review.setTitle("Name");
review.setHtmlContent("Test".getBytes("UTF8"));
// Важная для демонстрации логики inverse строчка
document.addReview(review);
// Непосредственно добавление
Transaction tx = HibernateUtil.currentSession().beginTransaction();
HibernateUtil.currentSession().save(review);
tx.commit();
DMSDocument document = (DMSDocument) PrefixObjectLoaderFacade.getObjectByUUID("coreboo2k04pg0000hd7rmt2hil696q4");
DMSReview review = new DMSReview();
review.setTitle("Name");
review.setHtmlContent("Test".getBytes("UTF8"));
// Важная для демонстрации логики inverse строчка
document.addReview(review);
// Непосредственно добавление
Transaction tx = HibernateUtil.currentSession().beginTransaction();
HibernateUtil.currentSession().save(review);
tx.commit();
При inverse=false у нас будут выполнены следующие SQL-запросы (посмотреть можно установив опцию hibernate.show_sql в true):
Hibernate:
INSERT
INTO
TBL_DMS_REVIEW
(TITLE, htmlContent, document, UUID)
VALUES
(?, ?, ?, ?)
Hibernate:
UPDATE
TBL_BO
SET
version=?,
Title=?,
creationDate=?,
removed=?,
removalDate=?,
ParentId=?
WHERE
UUID=?
AND version=?
Hibernate:
UPDATE
TBL_DMS_REVIEW
SET
document=?
WHERE
UUID=?
INSERT
INTO
TBL_DMS_REVIEW
(TITLE, htmlContent, document, UUID)
VALUES
(?, ?, ?, ?)
Hibernate:
UPDATE
TBL_BO
SET
version=?,
Title=?,
creationDate=?,
removed=?,
removalDate=?,
ParentId=?
WHERE
UUID=?
AND version=?
Hibernate:
UPDATE
TBL_DMS_REVIEW
SET
document=?
WHERE
UUID=?
Вот отсюда начинается самое интересное - алгоритм добавления сущностей в БД.
1. Сначала сохраняется сущность DMSReview. Именно здесь нас подстерегает основная опастность: хорошо если соответствующая полю document запись в БД уже есть. А если записи нет, то в поле document будет записан NULL, но создавая файл-мэппинга мы указали ограничение not-null="true", соответсвенно при генерации DDL (Data Definition Language) по файлу мэппинга будет создано ограничение целостности (поле document не может принимать значение NULL), произойдет ошибка и выполнится откат транзакции.
2. Так как мы добавили следующий код:
// Важная для демонстрации логики inverse строчка
document.addReview(review);
document.addReview(review);
то происходит обновление записи о документе. Хибернейт не отслеживает какие поля менялись и менялись ли вообще (в частности, приведенная выше строчка вообще не меняет ни одно поле в таблице tbl_dms_document). Он "видит", что объет изменился и просто делает его апдейт. Соответственно в случае, если объект DMSDocument не изменяется в рамках транзакции, то и данного запроса не будет.
3. Происходит изменение записи о рецензии. Ей наконец-то устанавливается нужное значение поля document - начинает работать ассоциация.
Теперь рассмотрим противоположную ситуацию: inverse="true".
Сначала рассмотрим, что происходит в данном случае. А происходит ни больше ни меньше как следующее:
Hibernate:
INSERT
INTO
TBL_DMS_REVIEW
(TITLE, htmlContent, document, UUID)
VALUES
(?, ?, ?, ?)
Hibernate:
UPDATE
TBL_BO
SET
version=?,
Title=?,
creationDate=?,
removed=?,
removalDate=?,
ParentId=?
WHERE
UUID=?
AND version=?
INSERT
INTO
TBL_DMS_REVIEW
(TITLE, htmlContent, document, UUID)
VALUES
(?, ?, ?, ?)
Hibernate:
UPDATE
TBL_BO
SET
version=?,
Title=?,
creationDate=?,
removed=?,
removalDate=?,
ParentId=?
WHERE
UUID=?
AND version=?
Как видим ситуация изменилась - пропал последний апдейт! Дело в том, что ассоциация теперь как бы инвертирована - она управляется не со стороны DMSDocument, а со стороны DMSReview! Соответственно, DMSReview уже на этапе первого insert "знает" к какому документу она относится и поле document сразу заполнено корректно. Поэтому надобность в последнем апдейте отпадает.
Если же объект DMSDocument не существует, а создается вместе с коллекцией объектов DMSReview, то сначала в базу будет записан он, соответственно у него появится id и объекты DMSReview будут опять таки сохранены с корректным значением поля document, естественно равным значению поля id сущности DMSDocument. Нарушения целостности в случае inverse="true" не будет.
Понравилось сообщение - подпишись на блог через RSS
Наглядно и хорошо оформленно. Классная статья.
ОтветитьУдалитьПавел, а подскажи чем ты highlighting посветку для кода генерируешь?
Большое спасибо за комментарий. Рад что статья оказалась полезной.
ОтветитьУдалитьПо поводу подцветки кода. Я использую quickhighlighter.com/. Он генерируем html-код, который можно скопировать из специального поля ввода. Из плюсов могу отметить - получается очень красиво, есть даже ссылки на javadoc-по библиотечным классам (например если нажать на слово "String"). Из минусов - необходимо внедрять css-код для выбранных языков в разметку блога. Соответственно т.к. получаемых html заточен на определенный css-код то корректно подцветка отображается только на страницах блога. В RSS уже не корректно. НО! Можно в quickhighlighter.com поставить галочку "Combine Style and HTML Code" и все стили будут внедрятся непосредственно в HTML. Кстати планирую опробовать эту фичу. Посмотрю как будет код смотреться в Яндекс-ленте.
Благодарствую за ссылку и описание сервиса. Думаю вполне потянет на отдельный пост, ибо для программеров-блоггеров очень полезная штукенция.
ОтветитьУдалитьОбязательно воспользуюсь сервисом, а то как-то совсем по другому статья выглядит когда код с подцветкой.
Спасибо за идею, если будет время - непременно реализую, напишу про различные системы подцветки кода. Согласен, что подцвеченный исходник хочется читать, в отличие от плохоотформатированной портянки.
ОтветитьУдалитьВообще на мысль заюзать подцветку меня навело мое первое сообщение с кодом на php. Блоггер не разрешает использовать символы < и >, а вспоминать таблицу html-сущностей не хотелось. Кстати еще один + в копилку сервиса - забываешь о сущностях. Если использовать подцветчики на базе JavaScript - так не получится.
Я вот очень плотно столкнулся с inverse="false"
ОтветитьУдалитьЕсть сущности - "язык" и "пользователь"
Каждый пользователь может
1) знать язык
2) учить язык
выходит, у меня двойная связь многие-ко-многим.
Итого, с учетом перехода к связям один-ко-многим получается 4 таблицы.
Гинерил маппиги в NetBeans по готовой базе.
Маппинг по юзеру:
но нетбинс, изначально, создал один set с inverse="false" а другой с inverse="true"
в итоге у меня при создании пользователя добавлялись только те языки которые он знал.
Спасибо за статью, пригодилась.
ОтветитьУдалить