Глава 19. Лучшие Практики Использования

Создавайте "мелкозернистые" (fine-grained) классы и отображайте их с использованием элемента мэппинга <component>.

Используйте класс Адрес для инкапсулирования следующей информации улица, пригород, штат, почтовый код. Данный подход приводит к повторному использованию кода и упрощает рефакторинг.

Для долгоживущих (persistent) классов, объявляйте свойства-идентификаторы.

В Hibernate свойства-идентификаторы являются опциональными, но имеется много причин, по которым вы должны их использовать. Мы рекомендуем, чтобы идентификаторы были 'синтетическими' (synthetic), т.е. не несущими никакой смысловой нагрузки для бизнеса; также рекомендуем, чтобы идентификторы были непримитивных типов. Для макскомальной гибокости, используйте java.lang.Long или java.lang.String.

Помещайте мэппинг каждого класса в отдельный файл.

Не используйте единственный монолитный документ-мэппинга. Отображайте класс com.eg.Foo в файле com/eg/Foo.hbm.xml. Это имеет особый смысл при работе в команде.

Загружайте мэппинги как ресурсы.

Размещайте мэппиги вместе с классами, для которых они созданы.

Рассмотрите вариант использования внешних строк запросов.

Это хорошая практика, если ваши запросы используют SQL-функции несоответствующие ANSI стандарту. Размещение строк запросов в мэппинг-файлах, делает приложение более портируемым.

Используйте связанные переменные (bind variables).

Как и в JDBC, всегда заменяйте неконстантые значения знаком "?". Никогда не используйте манипуляций со строками, для вставки неконстантых значений в запрос! Или еще лучше, рассмотрите вариант использования именных параметров (named parameters) в запросах.

Не создавайте и не управляйте своими собственными JDBC-соединениями.

Hibernate позволяет приложениям управлять JDBC-соединенями, но данный подход должен использоваться только в самых крайних случаях. Если вы не можете использовать встроенные провайдеры соединений (connections providers), обдумайте вариант с использованием вашей собственной реализации net.sf.hibernate.connection.ConnectionProvider.

Обдумайте использование пользовательских типов (custom types).

Предположим у вас имеется Java-тип, скажем из какой-либо библиотеки, который вам необходимо сделать долгоживущим, но этот тип не предоставляет методов доступа (accessors) необходимых для того чтобы замэпить его как компонент (component). Вы должны рассмотреть вариант с реализацией net.sf.hibernate.UserType. Данный подход освобождает код пиложения от необходимости преобразований Java-типа в Hibernate-тип и наоборот.

Используйте написанный руками JDBC-код в "узких местах" вашей системы.

В критических по производительности местах вашей системы, при выполнении некоторых видов операций (например массовые изменения/удаления), ваша система может получить выигрыш в производительности от использования JDBC-вызовов напрямую. Но пожалуйста удостоверьтесь что это на самом деле "узкое место" вашей системы. И не стоит считать что непосредственное использование JDBC-вызовов обязательно быстрее. Если вам необходимо использовать JDBC-вызовы напрямую, то наиболее правильное решение, это открыть новую Hibernate-сессию и использовать её JDBC-соединение. Таким образом вы можете использовать единую стратегию транзакций и нижележащий провайдер соединений.

Разберитесь с концепцией Session.flush().

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

В приложениях с трёхзвенной архитектурой, обдумайте вариант использования saveOrUpdate().

При использовании servlet / session bean архитектуры, вы можете передавать долгоживущие объекты, загруженные в session bean'е, в servlet / JSP слой. Используйте новую Hibernate-сессию для обработки каждого запроса. Используйте вызов Session.update() или Session.saveOrUpdate() для обновления состояния долгоживущего объекта.

В приложениях с двухзвенной архитектурой, обдумайте использование отключения сессии (session disconnection).

Для достижения наилучшего масштабирования приложения, Транзакции Уровня Баз Данных (Database Transactions) должны быть по возможности наиболее короткими. Тем не менее, часто необходимо реализовать долговыполняющиеся Транзакции Уровня Приложения (long running Application Transactions), которые со стороны пользователя выглядят как единая единица-работы (single unit-of-work). Данные Транзакции Уровня Приложения (Application Transaction) должны быть выполнены в нескольких циклах запрос-ответ. Используйте либо Отсоединенные Объекты (Detached Objects), либо, в двухзвенных приложениях, просто отсоедините (Session.disconnect()) Hibernate-сессию от используемого JDBC-соединения и переподсоединяйте сессию (Session.reconnect()) для каждого последующего запроса. Никогда не используйте единую Hibernate-сессию для более чем одной Транзакции Уровня Приложения (Application Transaction usecase); Иначе вы можете столкнуться с использованием устаревших данных (stale data), т.к. Hibernate-сессия является кэшем первого уровня.

Не стоит трактовать исключительные ситуции (exceptions) как восстанавливаемые (recoverable).

Это скорее необходимая, чем "лучшая" практика использования. Когда случается исключительная ситуация, откатите текщую транзакцию (Transaction.rollback()) и закройте Hibernate-сессию (Session.close()). Если вы этого не сделаете, Hibernate не может гарантировать что данное состояние объектов в памяти (в сессии) точно отображает состояние объектов в базе данных. Как один из особых случаев, не используйте Session.load() для определения существует ли в базе данных объект с заданным идентификатором; взамен, используйте вызов find() или get(). Некоторые исключения, являются восстанавливаемыми (recoverable), например, StaleObjectStateException и ObjectNotFoundException.

Для ассоциаций предпочитайте стратегию "ленивой" выборки данных (lazy fetching).

Обдуманно используйте стратегию полной выборки (eager, outer-join) данных. Используйте прокси (proxy) классы и/или "ленивые" коллекции для большинства ассоциаций с классами, которые не кэшируютя на уровне JVM (JVM-level cache). Для ассоциаций с кэшируемыми классами, для которых имеется большая вероятность удачного обращения в кэш (cache hit), явно отключайте стратегию полной выборки (eager fetching), используя outer-join="false". В случае когда выборка с использованием outer-join уместна, используйте HQL запрос с ключевым словом left join fetch.

Рассматрите варианты абстрагирования вашей бизнес-логики от Hibernate.

Спрячьте за интерфейсом весь (Hibernate) код, в котором происходит обращение к данным (data-access code). Используйте совместно следующие шаблоны DAO и Thread Local Session. Вы также можете написать JDBC-код для чтения, сохранения ваших долгоживущих классов, ассоциированных с Hibernate посредством UserType. (Данный совет предназначен для "достаточно больших" приложений; он не подходит для приложений с пятью таблицами!)

При реализации equals() и hashCode(), используйте уникальный бизнес-ключ (unique business key).

Если вам необходимо сравнивать объекты за пределами сессии, вы должны реализовать методы equals() и hashCode() для ваших объектов (shl: я так понимаю что авторы имели ввиду что два объекта загруженные в одной сессии можно сравнивать через identity, т.е. с использованием оператора '=='). Внутри одной сессии, индивидуальность (idenity) Java-объектов гарантирована. Если вы реализуете эти методы, не вздумайте использовать идентификаторы базы данных (поля которые отображаются в колонки, являющиеся идентификаторами БД). Временный объект, объект еще не сохраненный в БД, не имеет присвоенного ему идентификатора БД, Hibernate присвоит его, когда объект будет сохранен в БД. Но если объект находится в java.util.Set'е в моммент сохранения, то его хэш-код (hash code) изменится, нарушая соглашение (contract) для hashCode(). Для реализации equals() и hashCode(), используйте уникальный бизнес-ключ, т.е. сравнивайте уникальные комбинации свойств класса. Запомните, что данный ключ должен быть неизменным и уникальным только до тех пор пока объект находится в java.util.Set'е, не на протяжении всего жизненного цикла объекта (т.е. бизнес-ключ не обязан быть постоянным как первичный ключ БД). Никогда не используйте коллекций при реализации equals(), из-за ленивой инициализации (lazy loading) и будьте осторожны с другими ассоциированными классами, которые могут иметь настроенные для них прокси (proxy).

Не используйте экзотических ассоциаций в мэппингах.

Хорошие примеры использования настоящих ассоциаций many-to-many, очень редки. Как правило, вам необходимо сохранять дополнительную информацию в "связывающей таблице" (link table). В этом случае, намного лучше использовать ассоциацию one-to-many на промежуточный "связывающий класс" (link class). На самом деле, мы думаем что большинство всех ассоциаций, являются ассоциациями one-to-many и many-to-one. Вы должны быть внимательны при использовании других видов ассоциаций, каждый раз перед использованием экзотической ассоциации, стоит спросить себя, так ли она вам необходима.