Hibernate в вопросах и ответах

1. Что такое Hibernate Framework?

Hibernate — это библиотека с открытым исходным кодом (open source) для Java, предназначенная для решения задач ORM (object-relational mapping, объектно-реляционного отображения). Она представляет собой свободное программное обеспечение, распространяемое на условиях GNU Lesser General Public License. Hibernate Framework имеет легкий в использовании каркас для отображения объектно-ориентированной модели данных в традиционные реляционные базы данных и предоставляет стандартные средства JPA.

2. Преимущества использования Hibernate Framework?

Библиотека Hibernate является одним из самых востребованных ORM фреймворков для Java, поскольку :

  • позволяет разработчику сосредоточиться на бизнес логике, не отвлекаясь на управление ресурсами;
  • предоставляет собственный язык запросов (HQL), внешне похожий на SQL. Необходимо отметить, что HQL полностью объектно-ориентирован и понимает такие принципы, как наследование, полиморфизм и ассоциации (связи);
  • может использовать также чистый SQL, а, следовательно, поддерживает возможность оптимизации запросов и работы с любым сторонним провайдером БД;
  • поддерживает JPA аннотации, что позволяет сделать реализацию кода независимой;
  • поддерживает разные уровни cache, а следовательно может повысить производительность;
  • поддерживает ленивую инициализацию используя proxy объекты и выполняя запросы к базе данных только по необходимости;
  • интегрируется с другими Java EE фреймворками; например, Spring Framework поддерживает встроенную интеграцию с Hibernate;
  • является широко распространенным open source продуктом. Благодаря этому доступны тысячи открытых статей, примеров, а также документация по использованию фреймворка.

3. Объекты Hibernate SessionFactory, Session и Transaction

SessionFactory Экземпляр SessionFactory создается методом buildSessionFactory (ServiceRegistry) объекта org.hibernate.Configuration и предназначен для получения объекта Session. Инициализируется SessionFactory один раз. Внутреннее состояние SessionFactory неизменно (immutable), т.е. он является потокобезопасным. Internal state (внутреннее состояние) включает в себя все метаданные об Object Relational Mapping, определяемые при создании SessionFactory.
SessionFactory также предоставляет методы для получения метаданных класса и статистики, типа данных о втором уровне кэша, выполняемых запросах и т.д.
Session Однопоточный объект, устанавливающий связь между объектами/сущностями приложения и базой данных. Сессия создается при необходимости работы с БД и ее необходимо закрыть сразу же после использования. Экземпляр Session является интерфейсом между кодом в java приложении и hibernate framework, предоставляя методы для операций CRUD.
Transaction Однопоточный объект, используемый для атомарных операций. Это абстракция приложения от основных JDBC или JTA транзакций. org.hibernate.Session может занимать несколько org.hibernate.Transaction в определенных случаях.

Пример использования объектов SessionFactory, Session, Transaction.

4. Конфигурационный файл Hibernate

Файл конфигурации hibernate.cfg.xml содержит информацию о базе данных (драйвер, пул подключений, диалект) и параметрах подключения к серверу БД (url, login, password). В качестве параметров подключения можно использовать как JDBC, так и JNDI. В файле конфигурации также определяются дополнительные параметры, которые будут использованы при работе с сервером БД, Так, здесь необходимо определить маппинги сущностей/классов.

Чтобы отобразить в консоли SQL-скрипты, генерируемые Hibernate, необходимо в hibernate.cfg.xml определить истиное значение свойства «show_sql». Помните, что это необходимо использовать только на уровне разработки и тестирования. В финальной версии свойство «show_sql» должно быть отключено.

Пример файла конфигурации связанных сущностей.

5. Файл mapping

Файл отображения (mapping file) используется для связи entity бинов с таблицами базы данных. Содержимое файла имеет формат XML. Файл mapping можно использовать вместо аннотаций JPA. Особенно он становится необходимым при использовании сторонних библиотек.

Пример файла отображения.

6. Важные аннотации для отображения в Hibernate

Наиболее употребительные аннотации Hibernate из пакета javax.persistence представлены в следующей таблице :

@EntityОпределение класса как сущность entity bean
@Table, @ColumnОпределение таблицы в БД и наименования колонки в таблице
@IdПоле Primary Key в сущности entity bean
@GeneratedValueОпределение стратегии создания основных ключей
@SequenceGeneratorОпределение генератора последовательности
@OneToMany, @ManyToOne, @ManyToMany.Определение связи между сущностными бинами

Подробнее об аннотациях в сущностных бинах.

7. Отличие методов openSession и getCurrentSession

Методы openSession и getCurrentSession объекта SessionFactory возвращают сессию Session.

Метод getCurrentSession объекта SessionFactory возвращает сессию, связанную с контекстом. Но для того, чтобы метод вернул не NULL, необходимо настроить параметр current_session_context_class в конфигурационном файле hibernate. Поскольку полученный объект Session связан с контекстом hibernate, то отпадает необходимость в его закрытии; он закрывается вместе с закрытием SessionFactory.

<property name="hibernate.current_session_context_class">
    thread
</property>

Метод openSession объекта SessionFactory всегда создает новую сессию. В этом случае необходимо обязательно контролировать закрытие объекта сессии по завершению всех операций с базой данных. Для многопоточной среды необходимо создавать новый объект Session для каждого запроса.

При загрузке больших объемов данных без удержания большого количества информации в кэше можно использовать метод openStatelessSession(), который возвращает Session без поддержки состояния. Полученный объект не реализует первый уровень кэширования и не взаимодействует со вторым уровнем. Сюда же можно отнести игнорирование коллекций и некоторых обработчиков событий.

8. Отличие методов get и load объекта Session

Для загрузки информации из базы данных в виде набора/коллекции сущностей объект Session имеет несколько методов. Наиболее часто используемые методы get и load. Метод get загружает данные сразу же при вызове, в то время как load использует прокси объект и загружает данные только тогда, когда это требуется на самом деле (при обращении к данным). В этом плане load имеет преимущество в плане ленивой загрузки данных.

Метод load вызывает исключение, если данные не найдены. Поэтому load нужно использовать только при уверенности в существовании данных. Если необходимо удостовериться в наличии данных в БД, то нужно использовать метод get.

9. Различные состояния Entity Bean

Сущность Entity Bean может находиться в одном из трех состояний :

transient Состояние сущности, при котором она не была связана с какой-либо сессией и не является persistent. Сущность может перейти в состояние persistent при вызове метода save(), persist() или saveOrUpdate() объекта сессии.
persistent Экземпляр сущности, полученный методами get() или load() объекта сессии, находится в состоянии persistent, т.е. связан с сессией. Из состояния persistent сущность можно перевести в transient после вызова метода delete() сессии.
detached Если объект находился в сотоянии persistent, но перестал быть связанным с какой-либо сессией, то он переходит в состояние detached. Такой объект можно сделать персистентным, используя методы update(), saveOrUpdate(), lock() или replicate().

Из состояний transient и detached объект можно перевести в состояние persistent в виде нового объекта после вызова метода merge().

10. Отличия методов save, saveOrUpdate и persist

Метод save используется для сохранения сущности в базе данных. Этот метод возвращает сгенерированный идентификатор. Возникаемые проблемы с использованием save связаны с тем, что метод может быть вызван без транзакции. А следовательно если имеется отображение нескольких связанных объектов, то только первичный объект будет сохранен, т.е. можно получить несогласованные данные.

Метод hibernate persist аналогичен save, но выполняется с транзакцией. Метод persist не возвращает сгенерированный идентификатор сразу.

Метод saveOrUpdate используется для вставки или обновления сущности. Если объект уже присутствуют в базе данных, то будет выполнен запрос обновления. Метод saveOrUpdate можно применять без транзакции, но это может привести к аналогичным проблемам, как и в случае с методом save.

11. Использование метода сессии merge

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

12. Отсутствие в Entity Bean конструктора без параметров

Hibernate использует рефлексию для создания экземпляров Entity бинов при вызове методов get или load. Для этого используется метод Class.newInstance, который требует наличия конструктора без параметров. Поэтому, в случае его отсутствия, будет вызвано исключение HibernateException.

13. Entity Bean не должна быть final

Hibernate использует прокси классы для ленивой (lazy) загрузки данных (т.е. не сразу, а по необходимости). Это достигается с помощью расширения Entity Bean. Отсюда следует, что если бы он был final, то это было бы невозможно.

Ленивая загрузка данных во многих случаях повышает производительность, а следовательно важна и от нее не следует отказываться.

14. Сортировка данных в Hibernate

При использовании алгоритмов сортировки из Collection API используется сортированный список (sorted list). Для маленьких коллекций это не приводит к излишнему расходу ресурсов. Однако на больших коллекциях это может привести к потере производительности и ошибкам OutOfMemory.

Entity Bean'ы для работы с сортированными коллекциями должны реализовывать интерфейс Comparable/Comparator. При использовании фреймворка Hibernate для загрузки данных можно применить Collection API и команду order by для получения сортированного списка (ordered list). Ordered list является лучшим способом получения sorted list, т.к. используется сортировка на уровне базы данных, работающая быстрее и не приводящая к утечке памяти. Пример запроса к БД для получения ordered list :

List<Employee> list = session.createCriteria(Employee.class)
                             .addOrder(Order.desc("id")).list();

Hibernate использует следующие типы коллекций : Bag, Set, List, Array, Map.

15. Использование Query Cache в Hibernate

Hibernate реализует область кэша для запросов ResultSet, который тесно взаимодействует с кэшем второго уровня Hibernate. Для подключения этой дополнительной функции необходимо определить истинное значение свойства hibernate.cache.use_query_cache в файле конфигурации hibernate.cfg.xml и в коде при обращении к БД использовать метод setCacheable(true). Кэшированные запросы полезны только при их частом исполнении с повторяющимися параметрами.

Определение свойства в файле конфигурации Hibernate :

<property name="hibernate.cache.use_query_cache">
    true
</property>

Формирование запроса с использованием метода setCacheable (true) :

Query query = session.createQuery("from Employee");
query.setCacheable(true);
query.setCacheRegion("ALL_EMP");

16. Язык запросов HQL

Hibernate включает мощный язык запросов HQL (Hibernate Query Language), который очень похож на родной SQL. В сравнении с SQL, HQL полностью объектно-ориентирован и использует понятия наследования, полиформизма и связывания.

HQL использует имя класса взамен имени таблицы, а также имя свойства вместо имени колонки. Пример HQL :

Query query = session.createQuery("from ContactEntity where firstName = :paramName");
query.setParameter("paramName", "Nick");
List list = query.list();

17. Нативный SQL-запрос в Hibernate

Для выполнения нативного запроса необходимо использовать SQLQuery, который может выполнять чистый SQL-запрос. Но необходимо учитывать, что в этом случае можно потерять все преимущества HQL (ассоциации, кэширование). Пример нативного SQL-запроса :

String sql  ;
Query  query;

sql   = "select id, name, salary from employees";

query = session.createSQLQuery(sql).addEntity(Employee.class);
List<Employee> list = query.list();

for (Iterator<Employee> it = users.iterator(); it.hasNext();) {
    Employee employee = (Employee) it.next();
    System.out.println(employee.toString());
}

Обратите внимание, что при формировании Query был добавлен класс Employee.class, в результате чего метод list() объекта Query вернул коллекцию сотрудников List<Employee>.

В следующем коде при формировании Query нет привязки к конкретному классу. В результате метод list() возвращает коллекцию объектов List<Object[]>.

query = session.createSQLQuery(sql);
List<Object[]> rows = query.list();
for(Object[] row : rows) {
    Employee emp = new Employee();
    emp.setId     (Long.parseLong    (row[0].toString()));
    emp.setName                      (row[1].toString());
    emp.setSalary (Double.parseDouble(row[2].toString()));
    System.out.println(emp);
}

18. Преимущества поддержки нативного SQL-запроса

Использование нативного SQL может быть необходимо при выполнении некоторых запросов к базам данных, которые могут не поддерживаться в Hibernate. Т.е. включение в запросы специфичных для БД «фишек».

19. Именованный запрос, Named SQL Query

Hibernate поддерживает использование именованных запросов, которые можно определить в одном месте и использовать в любом месте в коде. Именованные запросы поддерживают как HQL, так и Native SQL. Для создания Named SQL Query можно использовать JPA аннотации @NamedQuery, @NamedNativeQuery или конфигурационный файл отображения (mapping files). Пример описания и использования Named SQL Query.

// Определение Named SQL Query
@NamedQuery(
    name = "getContacts",
    query = "select ce from ContactEntity ce where ce.id >=:id"
)

// Сущность
@Entity
@Table(name="contact", schema = "", catalog = "javastudy")
public class ContactEntity {
}
. . .
// использование Named SQL Query
Query query = session.getNamedQuery("getContacts")
                     .setString("id", "10");

20. Преимущества именованных запросов Named SQL Query

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

Одним из основных недостатков именованного запроса является то, что его сложнее отлаживать. Сложности могут быть связаны с поиском места определения запроса. Поэтому не разбрасывайтесь описанием запросов в различных участках. Можно все связанные с сущностью запросы описать непосредственно в классе, используя аннотацию @NamedQueries, как это показано в следующем коде :

@Entity
@Table(name="student")

@NamedQueries({
    @NamedQuery(name="selectStudents",
                query="select st from Student st"),
    @NamedQuery(name="updateStudentRecord",
                query="update Student st set st.sname =: name
                           where st.id =:id")
})

public class Student {
    . . .
}

21. Использование org.hibernate.Criteria

Hibernate Criteria API представляет альтернативный подход HQL и позволяет выполнять запросы в БД без написания SQL кода. Для создания экземпляров Criteria используется класс Session. Пример Criteria с необязательным обрамлением в транзакцию :

session.beginTransaction();

List<User> users
users = session.createCriteria(User.class)
               .setMaxResults(10).list();
         
session.getTransaction().commit();

Приведенный выше запрос вернет первые 10 записей из таблицы сущности User. Метод setMaxResults представляет собой аналог команды LIMIT в SQL-запросе. Чтобы прочитать определенное количество записей с с определенной позиции (LIMIT 2, 15) необходимо дополнительно использовать метод setFirstResult :

List<User> users
users = session.createCriteria(User.class)
               .setFirstResult(2)
               .setMaxResults(15).list();

Подробнее о org.hibernate.Criteria можно прочитать здесь.

22. Hibernate proxy и ленивая загрузка (lazy load)

Hibernate может использовать прокси для поддержки отложенной загрузки. При соответствующем атрибут fetch аннотации связи (fetch определяет стратегию загрузки дочерних объектов) из базы данных не загружаются связанные объекты. При первом обращении к дочернему объекту с помощью метода get, если связанная сущность отсутствует в кэше сессии, то прокси код перейдет к базе данных для загрузки связанной сущности. Для этого используется javassist, чтобы эффективно и динамически создавать реализации подклассов Entity Bean объектов.

Подробнее об атрибуте загрузки связанных объектов fetch.

23. Каскадные связи

При наличии зависимостей (связей) между сущностями необходимо определить влияние различных операций одной сущности на связанные. Это можно реализовать с помощью аннотации каскадных связей @Cascade. Пример использования @Cascade :

import org.hibernate.annotations.Cascade;
 
@Entity
@Table(name = "EMPLOYEE")
public class Employee
{
    @OneToOne (mappedBy="employee")
    @Cascade (value=org.hibernate.annotations.CascadeType.ALL)
    private Address address; 
}

Помните, что имеются некоторые различия между enum CascadeType в Hibernate и в JPA. Поэтому обращайте внимание на импортируемый пакет при использовании аннотации и константы типа. Наиболее часто используемые CascadeType перечисления описаны ниже :

  • None : без каскадирования, т.е. никакая операция для родителя не будет иметь эффекта для ребенка;
  • ALL : все операции родителя будут отражены на ребенке (save, delete, update, evict, lock, replicate, merge, persist);
  • SAVE_UPDATE : операции save и update, доступно только для hibernate;
  • DELETE : в Hibernate передается действие native DELETE;
  • DETATCH, MERGE, PERSIST, REFRESH, REMOVE – для простых операций;
  • LOCK : передает в Hibernate native LOCK действие;
  • REPLICATE : передает в Hibernate native REPLICATE действие.

24. Управление транзакциями

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

Любое вызываемое в Hibernate исключение автоматически вызывает rollback.

Использование JNDI DataSource в Hibernate

При использовании Hibernate в WEB-приложении и определении соответствующих настроек в контейнере сервлетов, касающихся подключения Datasource, можно использовать JNDI. Для этого необходимо в файле конфигурации hibernate.cfg.xml определить свойство hibernate.connection.datasource :

<property name="hibernate.connection.datasource">
    java:comp/env/jdbc/MyLocalDB
</property>
  Рейтинг@Mail.ru