Вступление
В сегодняшней IT-индустрии хранение и извлечение данных приложений базируется на объектно-ориентированных языках программирования и реляционных базах данных. Базы данных хранят большой объем информации в одном месте. Наряду с этим, они предоставляют эффективный способ поиска записей, чтобы легко и быстро добраться до нужных данных.
Но при работе с объектами есть одна функция, которую база данных не поддерживает, — это хранение самих объектов, поскольку в ней хранятся только реляционные данные. Данные в виде объектов обеспечивают абстракцию и переносимость.
Существует ли метод, с помощью которого возможно напрямую хранить и извлекать объекты в реляционной базе данных? Ответ — да, такая возможность есть. Название этой техники — ORM.
Объектно-реляционное отображение (Object Relational Mapping, ORM)
ORM устраняет несоответствие между объектной моделью и реляционной базой данных. Само название предполагает отображение объектов в реляционные таблицы. Технология ORM преобразует объекты в реляционные данные и обратно. Возникает вопрос: как это делается?
По сути, имя переменной экземпляра присваивается имени столбца, а его значение формирует строку в реляционной базе данных. Теперь вопрос в том, нужно ли объявлять таблицу в базе данных для хранения объектов. Ответ — нет, ORM сделает это за нас, объявив типы данных для столбцов такими же, как и для переменных экземпляра.
Однако для этого нужно выполнить небольшую настройку и сообщить ORM, как будут отображаться объекты. В Java ORM работает с обычными старыми классами объектов Java (POJO), объекты которых необходимо сопоставить. Эти классы по сути состоят из частных переменных экземпляра, параметризованного конструктора с общедоступными геттерами и сеттерами.
Наряду с параметризованным конструктором класс POJO должен иметь открытый конструктор без аргументов, поскольку он необходим ORM для сериализации. Поскольку параметризованный конструктор присутствует, compile не будет добавлять конструктор аргументов самостоятельно, поэтому придется делать это вручную.
Что такое Hibernate?
Hibernate — это высокопроизводительный инструмент объектно-реляционного сопоставления с открытым исходным кодом для языка программирования Java. Он был выпущен в 2001 году Гэвином Кингом и его коллегами из Cirrus Technologies в качестве альтернативы Entity Beans (объектным бинам) в стиле EJB2.
Этот фреймворк отвечает за решение проблем несоответствия объектно-реляционного импеданса. В Java есть спецификация под названием Java Persistence API (JPA), которая описывает управление объектами в реляционной базе данных. Hibernate — это всего лишь реализация JPA.
Архитектура Hibernate
Обсудим компоненты, из которых состоит Hibernate. Hibernate имеет многоуровневую архитектуру и позволяет работать, даже не зная базовых API, реализующих объектно-реляционное сопоставление. Hibernate находится между Java-приложением и базой данных. Архитектура Hibernate подразделяется на четыре уровня:
- уровень Java-приложения;
- уровень фреймворка;
- уровень API;
- уровень базы данных.
На приведенной выше диаграмме показаны все четыре слоя. Поток данных между Java-приложением и базой данных выполняется с использованием постоянного объекта, определенного Hibernate. Уровень инфраструктуры Hibernate состоит из различных объектов, таких как конфигурация, фабрика сеансов, сеанс, транзакция, запрос и критерии. Эти объекты создаются вручную, по мере необходимости.
Объект конфигурации. Первый объект Hibernate, который должен присутствовать в любом Hibernate-приложении. Он активирует платформу Hibernate. Объект конфигурации создается только один раз во время инициализации приложения. Это родительский объект — именно из него создаются все остальные. Он проверяет, является ли файл конфигурации синтаксически правильным или нет. Он предоставляет свойства конфигурации и сопоставления, необходимые Hibernate.
Объект фабрики сеансов. Фабрика сеансов — это массивный потокобезопасный объект, используемый несколькими потоками одновременно. Таким образом, в приложении он должен быть создан только один раз и сохранен для последующего использования. Для каждой базы данных необходим отдельный объект фабрики сеансов. Фабрика сеансов отвечает за настройку Hibernate посредством свойств конфигурации, предоставляемых объектом конфигурации.
Объект сеанса. Облегченный объект, который создается каждый раз, когда нужно взаимодействовать с базой данных. Постоянные объекты сохраняются и извлекаются с помощью объекта сеанса. Это не потокобезопасный объект, поэтому его следует уничтожить после завершения взаимодействия.
Объект транзакции. Представляет собой единицу работы с базой данных. Это необязательный объект, но его следует использовать для обеспечения целостности данных и в случае возникновения какой-либо ошибки — выполнять откат.
Объект запроса. Объект запроса нужен для записи запросов и выполнения операций CRUD в базе данных. Можно написать запрос на SQL или воспользоваться языком запросов Hibernate (HQL). В процессе реализации вам станет известно о HQL больше.
Объект критериев. С его помощью выполняются объектно-ориентированные запросы для извлечения объектов из базы данных.
Как настроить Hybernate?
Во-первых, нужно установить Hibernate у себя в проекте. Можно загрузить jar-файл или воспользоваться более удобным способом, например, применить maven. Так и поступим. Загрузим зависимость hibernate, определенную в POM.xml.
Фреймворк Hibernate должен знать, как сопоставлять объекты, учетные данные сервера базы данных, где будут храниться и откуда будут извлекаться объекты, и еще несколько свойств. Вопрос в том, как передать всю эту информацию в Hibernate.
Эта настройка выполняется с помощью двух XML-файлов.
- hibernate.cfg.xml содержит свойства гибернации, такие как диалект, класс драйвера, URL-адрес сервера базы данных, имя пользователя и пароль.
- Файл сопоставления содержит взаимосвязь сопоставления между Java-объектами и таблицами базы данных. Имя файла должно соответствовать шаблону <тип объекта>.hbm.XML. Например, если мы хотим работать с объектом типа “Employee”, имя файла будет Employee.hbm.xml.
XML-файлы необходимо поместить в папку META-INF внутри src/main/resources . Третий компонент, который понадобится в настройке, — это класс POJO, который мы обсуждали ранее.
Рассмотрим пример работы с объектом Flight. В качестве переменных экземпляра он содержит идентификатор (ID), номер рейса, место отправления, место прибытия, дату рейса и тариф. Идентификатор будет первичным ключом таблицы.
Приведенное выше изображение — это hbm.cfg.xml. Как видите, все свойства определены внутри тега hibernate-configuration . Внутри тега session-factory указываются свойства базы данных, к которой необходимо подключиться.
Свойство hbm2ddl.auto проверяет или экспортирует язык определения данных схемы при создании объекта фабрики сеансов. Здесь мы прописали операцию обновления, которая обновит базу данных, не затрагивая ранее записанные данные. Недостает еще нескольких, таких как проверка, создание и создание-удаление.
Приведенный выше XML-код относится к файлу сопоставления. Все свойства определены внутри тега hibernate-mapping . Тег class предназначен для определения имени класса POJO и имени таблицы, которые будут созданы внутри базы данных в качестве атрибутов, а отношения сопоставления — в качестве значений.
Первичный ключ определяется внутри тега id . Мы определяем имя переменной экземпляра внутри атрибута name . Имя столбца и тип данных указаны в атрибутах column и type соответственно. Теги generator предоставляют стратегию, с помощью которой необходимо выполнить автоинкремент. Все остальные сопоставления переменных экземпляра предоставляются внутри тегов свойств property .
Настало время, когда нужно создать класс POJO для рейсов ( Flights ), объекты которого будут сохраняться в таблице FLIGHTS. Здесь есть также пустой конструктор по умолчанию, необходимость которого мы обосновали ранее.
Один вопрос, который наверняка приходит вам в голову: если мы, Java-разработчики, пользуемся инструментарием Java, зачем выполнять настройку с использованием XML? Есть ли какой-нибудь другой способ? Ответ таков: да, но только частично.
От файла сопоставления можно избавиться с помощью аннотаций, но конфигурационный XML-файл все еще необходим. Аннотации — мощный способ определения взаимосвязи между объектом и реляционной моделью. Они начинаются с @ .
Нужно просто добавить аннотации внутри класса POJO. Отдельного файла не требуется:
Аннотацию необходимо указывать непосредственно над объявлением поля.
- @Entity сообщает Hibernate, что этот класс — компонент сущности, и его объекты должны быть постоянными.
- @Table применяется для указания имени создаваемой таблицы в базе данных.
- @Id используется для определения первичного ключа. Можно также добавить совместно несколько полей, чтобы создать составной ключ.
- @GeneratedValue определяет стратегию инкремента в поле. Это необязательный параметр — если он не определен с помощью @Id , применяется стратегия по умолчанию.
- @Column определяет, как поле сопоставляется со столбцом в таблице. Аннотация принимает такие атрибуты, как имя столбца, определение столбца, возможность принимать null-значение, уникальность и т. д. В отличие от XML-файлов, здесь не нужно указывать тип, поскольку он берется непосредственно из поля.
Реализация Hibernate
Попробуем, наконец, выполнить CRUD-операции с помощью Hibernate. Для этого создадим класс с именем DAO . Но еще до этого нам понадобится объект фабрики сеансов. Есть два способа его создания. Если мы работаем без аннотации, то поступим вот так:
Другой способ будет определен в приведенном ниже примере. Процедура создания всех остальных объектов одинакова для обоих вариантов.
Первый пример — добавление объектов рейса Flight в базу данных. Создаем метод addFlight , который сохраняет объект в базе данных. Далее выполняется транзакция в режиме try-catch , чтобы в случае возникновения какой-либо проблемы удалось выполнить откат, а в противоположном случае — транзакция была зафиксирована в таблице.
Операция сохранения
Метод сохранения в объекте сеанса выполняет задачу вставки объекта.
Операция обновления
Теперь продвинемся вперед и попробуем обновить уже существующие в таблице данные.
Мы создали метод updateFlightNumber , в котором сначала извлекаем объект Flight с заданным идентификатором, а затем обновляем номер рейса методом-сеттером. После этого вызываем update -метод, чтобы обновить существующую сущность.
Примечание: если вам хочется вывести объекты, извлеченные из базы данных, на консоль, добавьте метод toString() в класс POJO, иначе выведется хэш-код.
Операция удаления
В базе данных есть только один объект с id=1 . Попытаемся удалить этот объект.
После выполнения метода deleteFlight таблица становится пустой. В приведенном выше коде мы сначала извлекли объект по его идентификатору и передали его методу удаления. Если все пойдет хорошо, транзакция окажется зафиксирована, в противном случае выполнится откат.
И еще одно: хотим ли мы обновить объект в базе данных или удалить его, нам понадобится значение его первичного ключа. Однако у нас не всегда есть возможность его получить. Кроме того, для этого необходимо выполнять сложные операции с данными. Цели можно достичь с помощью HQL, аналогичного SQL, но он удаляет шаблонный код. Это означает, что теперь не нужно использовать операторы select с запросами.
Основы Hibernate
Хочу начать со слов благодарности тому человеку, который мне вчера накинул кармы, позволив этим писать мне в персональный блог.
Долго думал, о чем же написать свой «первый» топик… Слово первый не зря взял в кавычки, так как первый топик на самом деле уже был, опыт был к сожалению неудачный — дело закончилось баном. Решил больше не копипастить. Уверенности тому, что надо написать что-то свое, придал вот этот топик. Решил твердо — пусть это будет и редко, но буду писать сам.
Совсем недавно, по роду свой деятельности, мне пришлось столкнуться с таким понятием как ORM — (англ. Object-relational mapping). В двух словах ORM — это отображение объектов какого-либо объектно-ориентированного языка в структуры реляционных баз данных. Именно объектов, таких, какие они есть, со всеми полями, значениями, отношениями м/у друг другом.
ORM-решением для языка Java, является технология Hibernate, которая не только заботится о связи Java классов с таблицами базы данных (и типов данных Java в типы данных SQL), но также предоставляет средства для автоматического построения запросов и извлечения данных и может значительно уменьшить время разработки, которое обычно тратится на ручное написание SQL и JDBC кода. Hibernate генерирует SQL вызовы и освобождает разработчика от ручной обработки результирующего набора данных и конвертации объектов, сохраняя приложение портируемым во все SQL базы данных.
Итак, перед нами стоит задача написать небольшое приложение, которое бы осуществляло простое взаимодействие с базой данных, посредством технологии Hibernate.
Немного подумав, решил написать так называемый «Виртуальный автопарк». Суть парка такова: есть автобусы, есть маршруты и есть водители. Автобусы и маршруты связаны отношением один ко многим, т.е. на одном маршруте может кататься сразу несколько автобусов. Водители и автобусы связаны отношением многие ко многим, т.е. один водитель может водить разные автобусы и один автобус могут водить разные водители. Вроде ничего сложного.
Вот схема базы данных.
За качество не ругайте — под рукой не оказалось нормального инструмента таблички рисовать…
Вот ссылка на дамп, снятый с базы, вдруг кто-то решит все это дело поднять 🙂
Приступаем к коду. Во первых нам необходимо описать классы наших сущностей, т.е. класс автобуса, водителя и маршрута.
Класс автобус.
import java.util.Set;
import java.util.HashSet;
public class Bus <
private Long id;
private String number;
private Set drivers = new HashSet();
private Long route_id;
public Bus() <
>
public void setId(Long id) <
this .id = id;
>
public void setNumber( String number) <
this .number = number;
>
public void setDrivers(Set drivers) <
this .drivers = drivers;
>
public void setRoute_id(Long route_id) <
this .route_id = route_id;
>
public Long getId() <
return id;
>
public String getNumber() <
return number;
>
public Set getDrivers() <
return drivers;
>
public Long getRoute_id() <
return route_id;
>
> * This source code was highlighted with Source Code Highlighter .
import java.util.Set;
import java.util.HashSet;
public class Driver <
private Long id;
private String name;
private String surname;
private int age;
private Set busses = new HashSet();
public Driver() <
>
public void setBusses(Set busses) <
this .busses = busses;
>
public Set getBusses() <
return busses;
>
public void setId(Long id) <
this .id = id;
>
public void setName( String name) <
this .name = name;
>
public void setSurname( String surname) <
this .surname = surname;
>
public void setAge( int age) <
this .age = age;
>
public Long getId() <
return id;
>
public String getName() <
return name;
>
public String getSurname() <
return surname;
>
public int getAge() <
return age;
>
> * This source code was highlighted with Source Code Highlighter .
import java.util.Set;
import java.util.HashSet;
public class Route <
private Long id;
private String name;
private int number;
private Set busses = new HashSet();
public Route() <
>
public void setId(Long id) <
this .id = id;
>
public void setName( String name) <
this .name = name;
>
public void setNumber( int number) <
this .number = number;
>
public void setBusses(Set busses) <
this .busses = busses;
>
public Long getId() <
return id;
>
public String getName() <
return name;
>
public int getNumber() <
return number;
>
public Set getBusses() <
return busses;
>
> * This source code was highlighted with Source Code Highlighter .
Заметьте, что все классы сущностей должны соответствовать Java naming conventions, т.е. у них должны быть обязательно геттеры, сеттеры и конструктор по умолчанию. Ничего сложного 🙂
Теперь для наших классов необходимо описать маппинг в виде xml-файлов, эти файлы как раз и будут отвечать за взаимодействие наших объектов с Hibernate и с базой данных.
Bus.hbm.xml
< hibernate-mapping >
< class name =«logic.Bus» table =«busses» >
< id column =«bus_id» name =«id» type =«java.lang.Long» >
< generator class =«increment» />
</ id >
< property column =«number» name =«number» type =«java.lang.String» />
< set name =«drivers» table =«busDriver» lazy =«false» >
< key column =«bus_id» />
< many-to-many column =«driver_id» class =«logic.Driver» />
</ set >
</ class >
</ hibernate-mapping > * This source code was highlighted with Source Code Highlighter .
Driver.hbm.xml
< hibernate-mapping >
< class name =«logic.Driver» table =«drivers» >
< id column =«driver_id» name =«id» type =«java.lang.Long» >
< generator class =«increment» />
</ id >
< property column =«name» name =«name» type =«java.lang.String» />
< property column =«surname» name =«surname» type =«java.lang.String» />
< property column =«age» name =«age» type =«java.lang.Integer» />
< set name =«busses» table =«busDriver» lazy =«false» >
< key column =«driver_id» />
< many-to-many column =«bus_id» class =«logic.Bus» />
</ set >
</ class >
</ hibernate-mapping > * This source code was highlighted with Source Code Highlighter .
< hibernate-mapping >
< class name =«logic.Route» table =«routes» >
< id column =«route_id» name =«id» type =«java.lang.Long» >
< generator class =«increment» />
</ id >
< property column =«name» name =«name» type =«java.lang.String» />
< property column =«number» name =«number» type =«java.lang.Integer» />
< set name =«busses» lazy =«false» >
< key column =«route_id» />
< one-to-many class =«logic.Bus» />
</ set >
</ class >
</ hibernate-mapping > * This source code was highlighted with Source Code Highlighter .
- Тег hibernate-mapping я думаю понятен, тут ничего говорить не стоит.
- Тег class имеет два параметра: параметр name — Имя класса (необходимо указывать полный путь с учетом структуры пакетов) и параметр table — имя таблицы в базе данных, на которую будет маппиться наш класс.
- Тег id описывает идентификатор. Параметр column указывает на какую колонку в таблице будет ссылаться поле id нашего объекта, так же указываем класс и указываем generator, который отвечает за генерацию id.
- Тег property описывает простое поле нашего объекта, в качестве параметров указываем имя поля, его класс и имя колонки в таблице.
- Тег set описывает поле в котором содержится некий набор(коллекция) объектов. Тег содержит параметр name — имя поля нашего объекта, параметр table — имя таблицы связи(в случае отношения многие ко многим) и параметр lazy. Lazy, если меня не подводит моя память, с английского — ленивый. Так называемые ленивые коллекци, сейчас постараюсь объяснить понятнее. Когда мы в параметре lazy указываем значечение false, то у нас при получении объекта Route из базы вместе с объектом достается и коллекция объектов Bus, так как busses это поле объекта Route. А если в качестве параметра мы указываем значение true, то коллекция объектов Bus не вытаскивается, для ее получения надо явно вызывать метод route.getBusses(). Вот предположим такой очень хороший пример. Есть объект город, в него входит массив районов, в каждый район — массив улиц, в каждую улицу — массив домов и так далее до людей, живущих в квартирах. Предположим мы хотим вытянуть из базы названия районов. Если укажем lazy = false, то помимо районом у нас вытянется еще огромный объем «ненужных» данных, если же lazy = true, то мы получим то что надо и ничего лигнего.
- Тег key имеет параметр column, который говорит, на какую колонку в таблице связи будет ссылаться поле нашего объекта.
- Тег many-to-many описывает связь типа многие ко многим, в качестве параметров тег использует column — имя колонки второй колонки в таблице связи и параметр class, указывающий какого класса будут объеты на той стороне.
Теперь создадим главный конфигурационный файл hibernate.cfg.xml, файл, откуда он будет дергать всю необходимую ему информацию.
< session-factory >
< property name =«connection.url» > jdbc:mysql://localhost/autopark </ property >
< property name =«connection.driver_class» > com.mysql.jdbc.Driver </ property >
< property name =«connection.username» > root </ property >
< property name =«connection.password» />
< property name =«connection.pool_size» > 1 </ property >
< property name =«current_session_context_class» > thread </ property >
< property name =«show_sql» > true </ property >
< property name =«dialect» > org.hibernate.dialect.MySQL5Dialect </ property >
< mapping resource =«logic/Bus.hbm.xml» />
< mapping resource =«logic/Driver.hbm.xml» />
< mapping resource =«logic/Route.hbm.xml» />
</ hibernate-configuration > * This source code was highlighted with Source Code Highlighter .
Тут я не буду особо вдаваться в объяснение, думаю многим и так все понятно 🙂 Скажу, что надо только в конце не забыть добавить тег mapping и указать в качестве параметра resources файлы конфигурации ваших бинов.
Теперь создадим класс, который будет хавать наш конфиг-файл и возвращать нам объект типа SessionFactory, который отвечает за создание hibernate-сессии.
import org.hibernate.cfg.Configuration;
import org.hibernate.SessionFactory;
public class HibernateUtil <
private static final SessionFactory sessionFactory;
static <
try <
sessionFactory = new Configuration().configure().buildSessionFactory();
> catch (Throwable ex) <
System.err.println( «Initial SessionFactory creation failed.» + ex);
throw new ExceptionInInitializerError(ex);
>
>
public static SessionFactory getSessionFactory() <
return sessionFactory;
>
> * This source code was highlighted with Source Code Highlighter .
Теперь нам осталось разобраться со взаимодействием нашего приложения с базой данных. Для этого для каждого класса-сущности, определим интерфейс, содержащий набор необходимых методов (Я приведу только один интерфейс и одну его реализацию, интерфейсы и реализации для др. классов подобны этим.)
import logic.Bus;
import logic.Driver;
import logic.Route;
import java.util.Collection;
import java.sql.SQLException;
public interface BusDAO <
public void addBus(Bus bus) throws SQLException;
public void updateBus(Long bus_id, Bus bus) throws SQLException;
public Bus getBusById(Long bus_id) throws SQLException;
public Collection getAllBusses() throws SQLException;
public void deleteBus(Bus bus) throws SQLException;
public Collection getBussesByDriver(Driver driver) throws SQLException;
public Collection getBussesByRoute(Route route) throws SQLException;
Теперь определим реализацию этого интерфейса в классе BusDAOImpl
import DAO.BusDAO;
import logic.Bus;
import logic.Driver;
import logic.Route;
import java.sql.SQLException;
import java.util.Collection;
import java.util. ArrayList ;
import java.util. List ;
import util.HibernateUtil;
import javax.swing.*;
import org.hibernate.Session;
import org.hibernate.Query;
public class BusDAOImpl implements BusDAO <
public void addBus(Bus bus) throws SQLException <
Session session = null ;
try <
session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
session.save(bus);
session.getTransaction().commit();
> catch (Exception e) <
JOptionPane.showMessageDialog( null , e.getMessage(), «Ошибка при вставке» , JOptionPane.OK_OPTION);
> finally <
if (session != null && session.isOpen()) <
public void updateBus(Long bus_id, Bus bus) throws SQLException <
Session session = null ;
try <
session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
session.update(bus);
session.getTransaction().commit();
> catch (Exception e) <
JOptionPane.showMessageDialog( null , e.getMessage(), «Ошибка при вставке» , JOptionPane.OK_OPTION);
> finally <
if (session != null && session.isOpen()) <
session.close();
>
>
>
public Bus getBusById(Long bus_id) throws SQLException <
Session session = null ;
Bus bus = null ;
try <
session = HibernateUtil.getSessionFactory().openSession();
bus = (Bus) session.load(Bus. class , bus_id);
> catch (Exception e) <
JOptionPane.showMessageDialog( null , e.getMessage(), «Ошибка ‘findById’» , JOptionPane.OK_OPTION);
> finally <
if (session != null && session.isOpen()) <
session.close();
>
>
return bus;
>
public Collection getAllBusses() throws SQLException <
Session session = null ;
List busses = new ArrayList <Bus>();
try <
session = HibernateUtil.getSessionFactory().openSession();
busses = session.createCriteria(Bus. class ).list();
> catch (Exception e) <
JOptionPane.showMessageDialog( null , e.getMessage(), «Ошибка ‘getAll’» , JOptionPane.OK_OPTION);
> finally <
if (session != null && session.isOpen()) <
session.close();
>
>
return busses;
>
public void deleteBus(Bus bus) throws SQLException <
Session session = null ;
try <
session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
session.delete(bus);
session.getTransaction().commit();
> catch (Exception e) <
JOptionPane.showMessageDialog( null , e.getMessage(), «Ошибка при удалении» , JOptionPane.OK_OPTION);
> finally <
if (session != null && session.isOpen()) <
session.close();
>
>
>
public Collection getBussesByDriver(Driver driver) throws SQLException <
Session session = null ;
List busses = new ArrayList <Bus>();
try <
session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Long driver_id = driver.getId();
Query query = session.createQuery(
» select b »
+ » from Bus b INNER JOIN b.drivers driver»
+ » where driver.id = :driverId »
)
.setLong( «driverId» , driver_id);
busses = ( List <Bus>) query.list();
session.getTransaction().commit();
> finally <
if (session != null && session.isOpen()) <
session.close();
>
>
return busses;
>
public Collection getBussesByRoute(Route route) <
Session session = null ;
List busses = new ArrayList <Bus>();
try <
session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Long route_id = route.getId();
Query query = session.createQuery( «from Bus where route_id = :routeId „ ).setLong( “routeId» , route_id);
busses = ( List <Bus>) query.list();
session.getTransaction().commit();
> finally <
if (session != null && session.isOpen()) <
session.close();
>
>
return busses;
>
Еще рас скажу, что реализации DriverDAOImpl и RouteDAOImpl будут аналогичны этой.
Наибольший интерес для нас представляют два последних метода, взгляните на них повнимательнее. Как происходит общение с базой? От объекта SessionFactory создается новая или получается текущая сессия, зачем начинается транзакция, выполняются необходимые действия, коммит транзакции и закрытие сессии. Вроде ничего сложного 🙂 Обратите внимание, на то, каким синтаксисом описан запрос к базе. Это так называемый HQL (Hibernate Query Language) HQL представляет собой объектно-ориентированный язык запросов, возможности его широки, но мной настолько широко еще не осилены 🙂 Помимо save, load, update, delete и HQL, можно пользоваться и обычным SQL. Например:
String query = «SELECT driver_id, name, surname, age FROM drivers»;
List drivers = new ArrayList();
drivers = (List) session.createSQLQuery(query).list();
Теперь создадим класс фабрики, к которой будем обращаться за нашими реализациями DAO, от которых и будем вызывать необходимые нам методы.
public class Factory <
private static BusDAO busDAO = null ;
private static DriverDAO driverDAO = null ;
private static RouteDAO routeDAO = null ;
private static Factory instance = null ;
public static synchronized Factory getInstance() <
if (instance == null ) <
instance = new Factory();
>
return instance;
>
public BusDAO getBusDAO() <
if (busDAO == null ) <
busDAO = new BusDAOImpl();
>
return busDAO;
>
public DriverDAO getDriverDAO() <
if (driverDAO == null ) <
driverDAO = new DriverDAOImpl();
>
return driverDAO;
>
public RouteDAO getRouteDAO() <
if (routeDAO == null ) <
routeDAO = new RouteDAOImpl();
>
return routeDAO;
>
> * This source code was highlighted with Source Code Highlighter .
Теперь нам осталось создать какой-либо демонстрационный класс, для того, чтобы посмотреть и опробовать все то, что мы написали. Ну, не будем тянуть, вот этот класс, возможно не самый удачный, но все же 🙂
public class Main <
public static void main( String [] args) throws SQLException <
Collection routes = Factory.getInstance().getRouteDAO().getAllRoutes();
Iterator iterator = routes.iterator();
System. out .println( «========Все маршруты======== #0000ff»>while (iterator.hasNext()) <
Route route = (Route) iterator.next();
System. out .println( «Маршрут : » + route.getName() + » Номер маршрута : » + route.getNumber());
Collection busses = Factory.getInstance().getBusDAO().getBussesByRoute(route);
Iterator iterator2 = busses.iterator();
while (iterator2.hasNext()) <
Bus bus = (Bus) iterator2.next();
System. out .println( «Автобус № » + bus.getNumber());
Collection busses = Factory.getInstance().getBusDAO().getAllBusses();
iterator = busses.iterator();
System. out .println( «========Все автобусы======== #0000ff»>while (iterator.hasNext()) <
Bus bus = (Bus) iterator.next();
Collection drivers = Factory.getInstance().getDriverDAO().getDriversByBus(bus);
Iterator iterator2 = drivers.iterator();
System. out .println( «Автобус № » + bus.getNumber());
while (iterator2.hasNext()) <
Driver driver = (Driver) iterator2.next();
System. out .println( «Имя : » + driver.getName() + » Фамилия: » + driver.getSurname());
>
> * This source code was highlighted with Source Code Highlighter .
Еще раз скажу, что может не самый удачный вариант использования всего нами написанного, но для этого уже лучше GUI писать или Web-интерфейс, а это уже другая песня 🙂
Hibernate ORM
Hibernate ORM enables developers to more easily write applications whose data outlives the application process. As an Object/Relational Mapping (ORM) framework, Hibernate is concerned with data persistence as it applies to relational databases (via JDBC). Unfamiliar with the notion of ORM? Read here.
JPA Provider
In addition to its own «native» API, Hibernate is also an implementation of the Java Persistence API (JPA) specification. As such, it can be easily used in any environment supporting JPA including Java SE applications, Java EE application servers, Enterprise OSGi containers, etc.
Idiomatic persistence
Hibernate enables you to develop persistent classes following natural Object-oriented idioms including inheritance, polymorphism, association, composition, and the Java collections framework. Hibernate requires no interfaces or base classes for persistent classes and enables any class or data structure to be persistent.
High Performance
Hibernate supports lazy initialization, numerous fetching strategies and optimistic locking with automatic versioning and time stamping. Hibernate requires no special database tables or fields and generates much of the SQL at system initialization time instead of at runtime.
Hibernate consistently offers superior performance over straight JDBC code, both in terms of developer productivity and runtime performance.
Scalability
Hibernate was designed to work in an application server cluster and deliver a highly scalable architecture. Hibernate scales well in any environment: Use it to drive your in-house Intranet that serves hundreds of users or for mission-critical applications that serve hundreds of thousands.
Reliable
Hibernate is well known for its excellent stability and quality, proven by the acceptance and use by tens of thousands of Java developers.
Hibernate в вопросах и ответах
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
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.
Метод 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 :
Hibernate использует следующие типы коллекций : Bag, Set, List, Array, Map.
15. Использование Query Cache в Hibernate
Hibernate реализует область кэша для запросов ResultSet, который тесно взаимодействует с кэшем второго уровня Hibernate. Для подключения этой дополнительной функции необходимо определить истинное значение свойства hibernate.cache.use_query_cache в файле конфигурации hibernate.cfg.xml и в коде при обращении к БД использовать метод setCacheable(true). Кэшированные запросы полезны только при их частом исполнении с повторяющимися параметрами.
Определение свойства в файле конфигурации Hibernate :
Формирование запроса с использованием метода setCacheable (true) :
16. Язык запросов HQL
Hibernate включает мощный язык запросов HQL (Hibernate Query Language), который очень похож на родной SQL. В сравнении с SQL, HQL полностью объектно-ориентирован и использует понятия наследования, полиформизма и связывания.
HQL использует имя класса взамен имени таблицы, а также имя свойства вместо имени колонки. Пример HQL :
17. Нативный SQL-запрос в Hibernate
Для выполнения нативного запроса необходимо использовать SQLQuery, который может выполнять чистый SQL-запрос. Но необходимо учитывать, что в этом случае можно потерять все преимущества HQL (ассоциации, кэширование). Пример нативного SQL-запроса :
Обратите внимание, что при формировании Query был добавлен класс Employee.class, в результате чего метод list() объекта Query вернул коллекцию сотрудников List<Employee>.
В следующем коде при формировании Query нет привязки к конкретному классу. В результате метод list() возвращает коллекцию объектов List<Object[]>.
18. Преимущества поддержки нативного SQL-запроса
Использование нативного SQL может быть необходимо при выполнении некоторых запросов к базам данных, которые могут не поддерживаться в Hibernate. Т.е. включение в запросы специфичных для БД «фишек».
19. Именованный запрос, Named SQL Query
Hibernate поддерживает использование именованных запросов, которые можно определить в одном месте и использовать в любом месте в коде. Именованные запросы поддерживают как HQL, так и Native SQL. Для создания Named SQL Query можно использовать JPA аннотации @NamedQuery, @NamedNativeQuery или конфигурационный файл отображения (mapping files). Пример описания и использования Named SQL Query.
20. Преимущества именованных запросов Named SQL Query
Named Query имеют глобальный характер, т.е. заданные в одном месте, доступны в любом месте кода. Синтаксис Named Query проверяется при создании SessionFactory, что позволяет заметить ошибку на раннем этапе, а не при запущенном приложении и выполнении запроса.
Одним из основных недостатков именованного запроса является то, что его сложнее отлаживать. Сложности могут быть связаны с поиском места определения запроса. Поэтому не разбрасывайтесь описанием запросов в различных участках. Можно все связанные с сущностью запросы описать непосредственно в классе, используя аннотацию @NamedQueries, как это показано в следующем коде :
21. Использование org.hibernate.Criteria
Hibernate Criteria API представляет альтернативный подход HQL и позволяет выполнять запросы в БД без написания SQL кода. Для создания экземпляров Criteria используется класс Session. Пример Criteria с необязательным обрамлением в транзакцию :
Приведенный выше запрос вернет первые 10 записей из таблицы сущности User. Метод setMaxResults представляет собой аналог команды LIMIT в SQL-запросе. Чтобы прочитать определенное количество записей с с определенной позиции (LIMIT 2, 15) необходимо дополнительно использовать метод setFirstResult :
Подробнее о org.hibernate.Criteria можно прочитать здесь.
22. Hibernate proxy и ленивая загрузка (lazy load)
Hibernate может использовать прокси для поддержки отложенной загрузки. При соответствующем атрибут fetch аннотации связи (fetch определяет стратегию загрузки дочерних объектов) из базы данных не загружаются связанные объекты. При первом обращении к дочернему объекту с помощью метода get, если связанная сущность отсутствует в кэше сессии, то прокси код перейдет к базе данных для загрузки связанной сущности. Для этого используется javassist, чтобы эффективно и динамически создавать реализации подклассов Entity Bean объектов.
Подробнее об атрибуте загрузки связанных объектов fetch.
23. Каскадные связи
При наличии зависимостей (связей) между сущностями необходимо определить влияние различных операций одной сущности на связанные. Это можно реализовать с помощью аннотации каскадных связей @Cascade. Пример использования @Cascade :
Помните, что имеются некоторые различия между 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, возвращающий ссылку, которую можно использовать для подтверждения или отката транзакции.