Hibernate на практике


Содержание

Связанные сущности в Hibernate

Статья является продолжением описания примера использования Sequence в Hibernate, в которой была рассмотрена только одна сущность и представлено описание сессии Session. В данной статье с использованием практического примера рассмотрим вопрос определения связей между сущностями.

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

Классы в Java могут не только наследоваться друг от друга, но и включать в себя в качестве полей другие классы или коллекции классов. В столбцах таблиц БД нельзя хранить сложные составные типы и коллекции таких типов (за некоторыми исключениями). Это не позволяет сохранять подобный объект в одну таблицу. Но можно сохранять каждый класс в свою собственную таблицу, определив необходимые связи между ними. C описания связей между объектами и начнем.

Определение связей между сущностями

Для определения связей между сущностями Hibernate использует аннотации @OneToOne, @OneToMany, @ManyToOne, @ManyToMany.

@OneToOne

Рассмотрим описание аннотации на примере, что каждый гражданин может иметь только один паспорт. И у каждого паспорта может быть только один владелец. Такая связь двух объектов в Hibernate определяется как @OneToOne (один-к-одному). В следующем листинге представлено описание двух сущностей Person (гражданин) и Password (паспорт). Лишние строки, не связанные с аннотацией @OneToOne, не включены в описания сущностей.

Для связи один к одному в обоих классах к соответствующим полям добавляется аннотация @OneToOne. Параметр optional говорит JPA, является ли значение в этом поле обязательным или нет. Связанное поле в User объявлено с помощью аннотации @JoinColumn, параметр name которой обозначает поле в БД для создания связи. Для того, чтобы объявить сторону, которая не несет ответственности за отношения, используется атрибут mappedBy в сущности Passport. Он ссылается на имя свойства связи (passport) на стороне владельца.

Со стороны владельца к аннотации @OneToOne добавляется параметр cascade. В однонаправленных отношениях одна из сторон (и только одна) должна быть владельцем и нести ответственность за обновление связанных полей. В этом случае владельцем выступает сущность User. Каскадирование позволяет указать JPA, что необходимо «сделать со связанным объектом при выполнении операции с владельцем». То есть, когда удаляется Person из базы, JPA самостоятельно определит наличие у него паспорта и удалит вначале паспорт, потом гражданина.

Связь в БД между таблицами users и passports осуществляется посредством поля passport_id в таблице users.

@OneToMany и @ManyToOne

Аннотации @OneToMany (один-ко-многим) и @ManyToOne (многие-к-одному) рассмотрим на примере гражданина и его места проживания. Гражданин имеет один основной адрес проживания, но по одному адресу могут проживать несколько человек. В следующем листинге представим эти сущности (лишние поля, не связанные с аннотациями, не отображаются) :

Владельцем в этом примере также будет класс Person, который имеет поле address, связанное с соответствующим объектом. Поскольку адрес у гражданина только один, то используется аннотация @ManyToOne. Аннотацией @JoinColumn определяется поле связи в таблице БД. Таким образом, параметры этих аннотаций несут такую же смысловую нагрузку, что и у связи @OneToOne.

А вот у владеемого объекта на этот раз всё иначе. Поскольку по одному адресу может проживать несколько жильцов, то поле tenants представлено коллекцией, которая имеет аннотацию @OneToMany. Параметр mappedBy также указывает на поле в классе владельца. Параметр fetch=FetchType.EAGER говорит о том, что при загрузке владеемого объекта необходимо сразу загрузить и коллекцию владельцев.

Для чтения связанных объектов из БД используются следующие стратегии загрузок (fetch) : EAGER и LAZY. В первом случае объекты коллекции сразу загружаются в память, во втором случае — только при обращении к ним. Оба этих подхода имеют достоинства и недостатки.

В случае FetchType.EAGER в памяти будут находиться все загруженные объекты, даже если нужен только один объект из десятка (сотен/тысяч). При использовании данной стратегии необходимо быть внимательным, поскольку при загрузке какого-нибудь корневого объекта, который связан со всеми остальными объектами и коллекциями, можно случайно попытаться загрузить в память и всю базу.

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

@ManyToMany

Примером ассоциации @ManyToMany (многие-ко-многим) могут быть отношения студентов и ВУЗов. В одном институте может быть много студентов, студент может учиться в нескольких ВУЗах. Рассмотрим с начала таблицы БД :

Для определения связи @ManyToMany в примере потребуется три таблицы : таблица студентов students, таблица ВУЗов university и таблица связей student_university, в которой будут связаны студенты и ВУЗы. Кроме этого в таблице student_university определены внешние ключи (FOREIGN KEY), предупреждающие появление непрошенных записей при отсутствии родительских.

Теперь можно представить описание сущностей :

Список институтов в сущности Student аннотирован с помощью @ManyToMany. Далее следует аннотация @JoinTable, которая определяет таблицу и поля для связи. Параметр name указывает название таблицы (student_university). Параметр joinColumns указывает на поле, которое используется для прямой связи (идентификатор student_id). Параметр inverseJoinColumns указывает на поле, которое используется для обратной связи (идентификатор university_id). Для указания столбцов связи из таблицы используется аннотация @JoinColumn.

Сущность университета University описана «зеркально».

Пример связанных сущностей

Рассмотрим пример использования аннотаций @OneToMane и @ManyToOne при определении связанных сущностей. В качестве первой сущности будет использоваться пользователь User. Второй сущностью будет автомобиль Auto. Пользователь может владеть несколькими автомобилями, поэтому сущность User будет связана с Auto связью @OneToMany (один-ко-многим). Сущность Auto будет связана с сущностью User связью @ManyToOne (многие-к-одному). Начнем с объектов базы данных :

SQL-скрипты создания таблиц пользователей и автомобилей

Записи таблицы пользователей ничего не знают о записях таблицы автомобилей. А записи таблицы Autos связаны с таблицей Users по внешнему ключу (поле user_id). Синтаксис описания внешних ключей в базах данных представлен здесь.

SQL-скрипт создания Sequence

Генератор последовательностей SEQ_USER используем для определении идентификаторов записей сущностей. Как работать с генераторами последовательностей Sequence в SQL подробно представлено здесь.

Проект тестирования связанных сущностей Hibernate

На следующем скриншоте представлена структура проекта hibernate-entities в среде разработки Eclipse. В проекте необходимо определить файл конфигурации hibernate.cfg.xml и классы-сущности (User и Auto). Модуль HibernateExample будет тестировать настройки hibernate и сущностей. Все библиотеки, необходимые для работы с Oracle и hibernate, размещены в поддиректории lib. После включения их в CLASSPATH они отображаются в корне проекта Eclipse.

Примечание : в демонстрационном примере hibernate был использован «файл-маппинг» person.cfg.xml сущности Person. В данном примере вместо «файл-маппингов» будем использовать аннотации Подробная информация об аннотациях JPA представлена здесь.

Конфигурация hibernate

В конфигурационном XML-файле hibernate.cfg.xml определяем сервер БД (драйвер, пул подключений, диалект, кодировку) и параметры подключения (url, login, password), а также дополнительные параметры, которые будут использованы при работе с сервером. В качестве сервера БД выбран Oracle c пулом подключений в одно соединение. В демонстрационном примере в качестве сервера БД использовался MySQL.

Дополнительно определяем истиное значение свойства «show_sql» для отображения в консоли SQL-скриптов, генерируемых библиотекой Hibernate. В заключении в обязательном порядке определяем маппинг сущностей/классов User и Auto, чтобы не вызывать исключений.

Листинг класса пользователя User

Описание сущности/класса User незначительно изменилось. Добавилось поле List autos, определяющее список автомобилей пользователя.

Описание аннотаций @Table, @Id, @Column, @GeneratedValue, @SequenceGenerator сущности User представлено в предыдущей статье. Здесь дополним список описанием аннотации
@OneToMany
Атрибут fetch в аннотации, определяющий стратегию загрузки дочерних объектов, может принимать одно из двух значений перечисления javax.persistence.FetchType :

  • FetchType.EAGER — загружать коллекцию дочерних объектов вместе с загрузкой родительских объектов;
  • FetchType.LAZY — загружать коллекцию дочерних объектов при первом обращении к ней (вызове метода get) — это так называемая отложенная загрузка.

Атрибут cascade обозначает, какие из методов интерфейса Session будут распространяться каскадно к ассоциированным сущностям. Возможные варианты : CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE. Необходимо правильно настроить CascadeType, чтобы не подгружать из базы данных лишних ассоциированных объектов-сущностей.

Листинг класса автомобиля Auto

При описании поля user используется аннотация @ManyToOne. Аннотация @JoinColumn определяет поле таблицы БД, по которому сущность Auto связана с пользователем User.

Листинг класса тестирования HibernateExample

HibernateExample используется для тестирования связей между сущностями Hibernate. Сначала создается сессия в методе createHibernateSession. При создании session устанавливается соединение с БД Oracle. Если сессия создана успешо, то в методе saveUser создаются два объекта (user1, user2), открывается транзакция и объекты сохраняются в БД. Для сохранения объектов используются методы save класса Session. После этого создаются два объекта типа Auto, у которых полям user присваивается значение первого пользователя. Объекты автомобилей сохраняются в БД и транзакция завершается.

После сохранения объектов в БД, пользователь user1 обновляется с использованием метода refresh() объекта сессии. Описание методов Session представлено здесь.

Выполнение примера

При выполнении примера в консоль выводится информация, представленная ниже. Поскольку установлен соответствующий флаг в файле конфигурации hibernate.cfg.xml, то формируемые библиотекой Hibernate SQL-скрипты также отображаются в консоли.

Информация, выведенная Hibernate в консоль, показывает, что сначала формируются SQL-скрипты (запросы к Sequence) для получения идентификаторов объектов пользователя и автомобиля, после этого создаются SQL-скрипты добавления пользователей и автомобилей в БД. И в заключение Hibernate создает SQL-скрипт select с использованием left outer join для обновления объектов.

«Распечатка» описаний пользователей показывет, что первый user имеет автомобили, второй — нет. Как Hibernate с использованием Sequence определяет значения идентификаторов подробно представлено в предыдущей статье.

В продолжении статьи рассмотрен вопрос чтения объектов с фильтрацией и без фильтрации.

Удаление связанных сущностей

Наличие или отсутствие связанной сущности в базе данных определяет способ удаления. Если связанная сущность отсутствует, то можно использовать оператор DELETE в HQL-запросе объекта Query. Но если сущность содержит связанный объект в таблице БД, то при выполнении транзакции удаления с использованием объекта Query будет вызвано соответствующее исключение. Удаление связанных сущностей необходимо выполнять с использованием объекта сессии Session. Подробнее об этом представлено при описании оператора DELETE в HQL-запросе.

Алексей Кутепов Разработчик программного обеспечения
Русский | English

Hibernate — библиотека предназначенная для решения задач объектно-реляционного отображения. Она представляет собой свободное программное обеспечение с открытым исходным кодом (open source), распространяемое на условиях GNU Lesser General Public License. Данная библиотека предоставляет легкий в использовании каркас (фреймворк) для отображения объектно-ориентированной модели данных в традиционные реляционные базы данных. Другими словами, используя данную библиотеку, можно заметно упростить себе жизнь, так как у нас появляется возможность эффективно и легко взаимодействовать с базами данных, не обладая глубокими знаниями SQL. Как использовать Hibernate я расскажу в этой статье.

В качестве примера мы с вами разработаем простой телефонный справочник, который будет хранить список контактов в базе данных (PostgreSQL), при этом у каждого контакта будет ещё список телефонных номеров.

Создайте Maven-проект в любой удобной для вас IDE, лично я предпочитаю Idea IntelliJ, или же можно сгенерировать проект с помощью Maven командой:

После того как проект создан, откройте файл pom.xml и отредактируйте его чтобы он выглядел так:

В блоке у нас идёт список зависимостей для нашего проекта. В него я включил библиотеки для работы с Hibernate и Spring, а так же библиотеку для работы с базой данных PostgreSQL.

Так же хочу обратить внимание на exec-maven-plugin. Он позволяет запускать наш проект средствами Maven, что будет для нас крайне полезно. Для этого в директории проекта достаточно выполнить команду:

Обратите внимание что мы сделали ссылку на main-класс для exec-maven-plugin, который пока что не создали: phone.book.main.Main .

Теперь создайте в каталоге /src/main/resources/META-INF/sping файл beans.xml со следующим содержанием:

В beans.xml мы сконфигурировали DataSource, который содержит всю необходимую информацию для подключения к базе данных: класс JDBC-драйвера (в данном случае драйвер для PostgreSQL), URL, логин и пароль.

Затем мы создаём бин hibernateSessionFactory, используя класс LocalSessionFactoryBean из библиотеки Spring ORM. Этот класс позволяет нам сконфигурировать все настройки Hibernate в спринге, и инициализирует класс SessionFactory, с помощью которого осуществляется взаимодействие с базой данных.

Для бина hibernateSessionFactory мы задаём параметры, которые используются для инициализации SessionFactory:

  • DataSource, без которого мы не сможем установить подключение к базе данных.
  • Классы-сущности Hibernate, в параметре «annotatedClasses«. Они представляют собой описание структуры таблиц базы данных в виде объекта. Эти классы мы скоро создадим.
  • Настройки Hibernate в параметре «hibernateProperties«. Тут 2 очень важных момента, а именно это SQLDialect (в данном случае PostrgeSQLDialect), который должен быть выбран или определён в соответствии с той базой данных, которую мы планируем использовать, и параметр «hibernate.hbm2ddl.auto» который определяет может ли Hibernate создавать таблицы или редактировать их структуру, если их нет или они отличаются о того что описано в классах-сущностях. У нас параметр «hibernate.hbm2ddl.auto» имеет значение «update«, то есть в случае отсутствия необходимых таблиц в базе данных Hibernate их создаст.

Последним шагом мы конфигурируем в спринге класс PersonDaoImpl и передаём ему объект SessionFactory в качестве параметра.

Теперь в каталоге /src/main/java создайте пакет phone.book.dao и поместите туда интерфейс и класс, что описаны ниже:

Интерфейс PersonDao.java:

Класс PersonDaoImpl.java:​

Класс PersonDaoImpl умеет всего 2 вещи: сохранять объект Person в базу данных и получать из базы данных все сохранённые объекты Person. Класс Person описывает структуру таблицы PERSON из нашей базы данных. Забегая вперёд, скажу что у нас ещё будет таблица PHONES. Давайте создадим классы для этих таблиц в пакете phone.book.model:

Класс Person.java:

Класс Phone.java:

Классы-сущности Person и Phone заслуживают особого внимания, как я уже говорил, они являются объектным представлением таблиц базы данных для фреймворка Hibernate. Любой класс-сущность должен начинаться с аннотации @Entity. Далее в коде следует аннотация @Table, содержащая в качестве параметра название таблицы, которую описывает класс-сущность. Аннотация @Id указывает на то, что данное поле является индексом, аннотация @Column определяет соответствие переменной конкретному столбцу таблицы, а аннотация @GeneratedValue указывает на то что значение этого поля будет генерироваться автоматически.

У нас есть ещё один очень интересный момент. Дело в том что таблицы PERSON и PHONES имеют связь один ко многим, то есть у одного человека может быть несколько номеров телефона. Эта связь описана с помощью аннотации @OneToMany.


У нас всё готово для работы с базой данных, теперь осталось написать класс, который будет использовать всё что у нас уже есть. Создайте пакет phone.book.main и поместите туда главный класс для нашего приложения:

Тут всё довольно просто: мы получаем контекст спринга с помощью ClassPathXmlApplicationContext, достаём из контекста наш ДАО, инициализируем классы сущности с константными значениями и сохраняем информацию из этих классов в базе данных. Затем выводим на экран все контакты, которые сохранены в базе данных.

На всякий случай выкладываю скриншот с общей структурой нашего проекта:

Чтобы увидеть результат работы, соберите проект с помощью команды:

Алексей Кутепов

Пример использования Hibernate.

Spring Framework — универсальный фреймворк с открытым исходным кодом для Java-платформы. Несмотря на то, что Spring Framework не обеспечивает какую-либо конкретную модель программирования, он стал широко распространённым в Java-сообществе главным образом как альтернатива и замена модели Enterprise JavaBeans. В нашем примере он будет использоваться для конфигурирования Hibernate. Если вы ранее не работали со Spring, то я так же настоятельно рекомендую с ним ознакомиться, так как на практике вам придётся довольно часто иметь с ним дело. Официальный сайт проекта http://spring.io.




В качестве примера мы с вами разработаем простой телефонный справочник, который будет хранить список контактов в базе данных, при этом у каждого контакта будет ещё список телефонных номеров.

В этой статье в качестве базы данных для проекта используется PostgreSQL.

Теперь перейдём непосредственно к разработке. Создайте Maven-проект в любой удобной для вас IDE, лично я предпочитаю Idea IntelliJ, или же можно сгенерировать проект с помощью Maven командой:

mvn archetype:generate \
-DarchetypeGroup > -Dgroup > -Dartifact > После того как проект создан, откройте файл pom.xml и отредактируйте его чтобы он выглядел так:

Давайте посмотрим что содержит наш pom.xml.

Первая важная деталь, это название нашего проекта, артефакт и версия. Зная эту информацию мы можем подключать наш проект как библиотеку к другому проекту, что довольно удобно. Слово SNAPSHOT говорит о том что проект ещё в разработке, поэтому в релизной версии слово SNAPSHOT нужно убрать, оставив только номер версии, например 1.0 .

создан для удобства. Сюда мы вынесли версии библиотек, которые используются в проекте.
Далее в блоке у нас идёт список зависимостей для нашего проекта. В него я включил библиотеки для работы с Hibernate и Spring, а так же библиотеку для работы с базой данных PostgreSQL:

Ну и последний блок содержит плагины, необходимые для нашего проекта:

Хочу обратить внимание на exec-maven-plugin. Он позволяет запускать наш проект средствами Maven, что будет для нас крайне полезно. Для этого в директории проекта достаточно выполнить команду:

Обратите внимание что мы сделали ссылку на main-класс для exec-maven-plugin, который пока что не создали: phone.book.main.Main .

Теперь создайте в каталоге /src/main/resources/META-INF/sping файл beans.xml со следующим содержанием:

В beans.xml мы сконфигурировали DataSource, который содержит всю необходимую информацию для подключения к базе данных: класс JDBC-драйвера (в данном случае драйвер для PostgreSQL), URL, логин и пароль.
Затем мы создаём бин hibernateSessionFactory, используя класс LocalSessionFactoryBean из библиотеки Spring ORM. Этот класс позволяет нам сконфигурировать все настройки Hibernate в спринге, и инициализирует класс SessionFactory, с помощью которого осуществляется взаимодействие с базой данных.
Для бина hibernateSessionFactory мы задаём проперти, которые используются для инициализации SessionFactory:

  • DataSource, без которого мы не сможем установить подключение к базе данных.
  • Классы-сущности Hibernate, в проперти «annotatedClasses«. Они представляют собой описание структуры таблиц базы данных в виде объекта. Эти классы мы скоро создадим.
  • Настройки Hibernate в проперти «hibernateProperties«. Тут 2 очень важных момента, а именно это SQLDialect (в данном случае PostrgeSQLDialect), который должен быть выбран или определён в соответствии с той базой данных, которую мы планируем использовать, и параметр «hibernate.hbm2ddl.auto» который определяет может ли Hibernate создавать таблицы или редактировать их структуру, если их нет или они отличаются о того что описано в классах-сущностях. У нас параметр «hibernate.hbm2ddl.auto» имеет значение «update«, то есть в случае отсутствия необходимых таблиц в базе данных Hibernate их создаст.

Последним шагом мы конфигурируем в спринге класс PersonDaoImpl и передаём ему объект SessionFactory в проперти.

Теперь в каталоге /src/main/java создайте пакет phone.book.dao и поместите туда интерфейс и класс, что описаны ниже:

Класс PersonDaoImpl умеет всего 2 вещи: сохранять объект Person в базу данных и получать из базы данных все сохранённые объекты Person. Класс Person описывает структуру таблицы PERSON из нашей базы данных. Забегая вперёд, скажу что у нас ещё будет таблица PHONES.
Давайте создадим классы для этих таблиц в пакете phone.book.model:

Классы-сущности Person и Phone заслуживают особого внимания, как я уже говорил, они являются объектным представлением таблиц базы данных для фреймворка Hibernate. Любой класс-сущность должен начинаться с аннотации @Entity. Далее в коде следует аннотация @Table, содержащая в качестве параметра название таблицы, которую описывает класс-сущность. Аннотация @Id указывает на то, что данное поле является индексом, аннотация @Column определяет соответствие переменной конкретному столбцу таблицы, а аннотация @GeneratedValue указывает на то что значение этого поля будет генерироваться автоматически.
У нас есть ещё один очень интересный момент. Дело в том что таблицы PERSON и PHONES связанны между собой связью «один ко многим», то есть у одного человека может быть несколько номеров телефона. Эта связь описана с помощью аннотации @OneToMany.

У нас всё готово для работы с базой данных, теперь осталось написать класс, который будет использовать всё что у нас уже есть. Создайте пакет phone.book.main и поместите туда главный класс для нашего приложения:

Тут всё довольно просто: мы получаем контекст спринга с помощью ClassPathXmlApplicationContext, достаём из контекста наш ДАО, инициализируем классы сущности с константными значениями и сохраняем информацию из этих классов в базе данных. Затем выводим на экран все контакты, которые сохранены в базе данных.

На всякий случай выкладываю скрин с общей структурой нашего проекта:

Чтобы увидеть результат работы, соберите проект с помощью команды:

Миграция унаследованных приложений, использующих Hibernate, на OpenJPA и EJB 3.0

Hibernate — некоммерческая библиотека с открытыми исходными кодами, предназначенная для взаимодействия с базами данных и предоставляющая возможность отображения обычных объектов Java™ (POJO) на таблицы реляционных СУБД (ORM), а также поддерживающая различные формы запросов к данным. Проект Apache OpenJPA похож по функционалу, также поставляется в исходных кодах и выполняет отображение объектов POJO согласно спецификации EJB 3.0 Java Persistence API. В статье сравниваются основные варианты использования Hibernate и Enterprise JavaBeans™ (EJB) 2.1 с эквивалентными сценариями, реализованными на основе OpenJPA и EJB 3.0. В частности, вы сможете поэтапно просмотреть и сравнить использование Hibernate, его объектно-реляционного отображения и конфигурации, с аналогичными элементами проектов на основе OpenJPA. Это сравнение не только подскажет вам, как выполнять подобные изменения, но и научит общему подходу к миграции унаследованных приложений, использующих Hibernate, на OpenJPA.

Несмотря на то, что основной упор в статье делается на миграцию унаследованных приложений с Hibernate на OpenJPA, в ней также содержатся ценные сведения для тех, кто уже знает Hibernate и хочет быстро перейти на новую спецификацию JPA, используя OpenJPA в качестве провайдера хранения данных для разработки новых проектов.

Предполагается, что читатели статьи знакомы с основными концепциями Hibernate, рассматриваемая версия реализации — Hibernate 3.0. Все примеры статьи компилируются и запускаются на Hibernate 3 и EJB 2.1, а также OpenJPA 0.9.7, входящем в состав IBM® WebSphere® Application Server V6.1 Feature Pack for EJB 3.0.

Есть множество причин для переноса унаследованных приложений c Hibernate на OpenJPA. Например, Hibernate не является стандартным решением для объектно-реляционного отображения и управления хранением данных. Hibernate 3 использует JDK 1.3.1 и выше. В противоположность ему OpenJPA реализует спецификацию JPA, которая является неотъемлемой частью спецификаций Java 5. Именно спецификация Java 5 была положена в основу WebSphere Application Server V6.1 Feature Pack for EJB 3.0. Подробнее об этих продуктах см. в Ресурсах.

В статье под аббревиатурой JPA подразумевается спецификация, а под аббревиатурой OpenJPA подразумевается конкретная реализация спецификации JPA.

Хотя в статье не рассмотрены все имеющиеся особенности и аспекты Hibernate, в ней описаны основные варианты использования.

Перенос приложений, использующих Hibernate

Спецификация Java Persistence API (JPA) была разработана как часть спецификации EJB 3.0 (JSR220) для того, чтобы предоставить сообществу Java единый стандартный механизм для хранения и обработки данных. JPA впитала в себя лучшие идеи Hibernate, TopLink, Java Data Objects и спецификации Container Managed Persistence (EJB-CMP) 2.1.

JPA может использоваться для обеих платформ Java, как Standard Edition (Java SE) так и Enterprise Edition (Java EE), поскольку она предполагает использование в качестве хранимых сущностей обычные объекты Java (POJO), процесс сохранения которых управляется провайдером JPA; одним из таких провайдеров является OpenJPA. Метаданные об отображении полей объекта на таблицы реляционных СУБД указываются в формате аннотаций Java 5 либо в XML-дескрипторах. Для сохранения Java-объектов в СУБД используется абстракция сущностей.

Создано уже довольно много реализаций провайдеров JPA. Реализация спецификации JPA от IBM основывается на проекте Apache OpenJPA. Благодаря появлению этих провайдеров JPA пользователи теперь могут использовать при программировании стандартный API, не выбирая между несовместимыми нестандартными реализациями провайдеров.

Чтобы помочь вам в миграции унаследованных приложений с Hibernate на OpenJPA, в этом разделе сравниваются часто используемые нестандартные API Hibernate с соответствующими API OpenJPA. В разделе содержится сравнение используемых классов и интерфейсов, а также примеры использования API в наиболее распространенных сценариях.

Детально все вышеописанное будет рассмотрено в следующих подразделах:

1. Классы и интерфейсы

В таблице ниже приведено сопоставление классов Hibernate с их близкими аналогами в OpenJPA. Все классы Hibernate находятся в пакете org.hibernate. Все интерфейсы JPA (в том числе и класс Persistence) относятся к пакету javax.persistence. Классы, реализующие интерфейсы спецификации JPA, находятся в пакетах org.apache.openjpa.*.

org.hibernate javax.persistence Описание
cfg.Configuration Persistence Предназначен для конфигурирования фабрики сессий (в Hibernate) или фабрики менеджеров сущностей (в OpenJPA). Как правило, используется для создания единичной фабрики сессий (или менеджера сущностей) в JVM.
SessionFactory EntityManagerFactory Предоставляет API создания сессий Hibernate (или менеджеров сущностей OpenJPA) для обработки запросов пользователей. Как правило, для каждого потока исполнения запросов пользователя создается своя сессия (или менеджер сущностей).
Session EntityManager Предоставляет API для хранения и загрузки сущностей в/из СУБД. Также используется для управления транзакциями и создания запросов к СУБД.
Transaction EntityTransaction Предоставляет API управления транзакциями.
Query Query Предоставляет API запуска и управления запросами.

2. Конфигурирование времени исполнения

Соглашения Hibernate

Конфигурирование времени исполнения Hibernate:

  • Определите статическую переменную типа SessionFactory.
  • Вызовите Configuration#configure().
  • Вызовите Configuration#buildSessionFactory().
Листинг 1. Конфигурация времени исполнения Hibernate

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

Существует несколько способов конфигурирования SessionFactory в Hibernate. Наиболее распространенный — вызов метода configure(). Если вы не передаете ему параметр name, то метод configure() ищет файл hibernate.cfg.xml в корневой директории Hibernate. Если вы передаете имя XML-файла конфигурации, то он также ищется в директориях приложения.

После нахождения файла конфигурации метод buildSessionFactory() создает объект SessionFactory и инициализирует его, используя данные из файла конфигурации.

Имейте в виду, что:

  • Вместо использования статической переменной некоторые приложения ищут SessionFactory в реестре JNDI, но вам все равно придется вызывать методы configure() и buildSessionFactory() при первом поиске, так что разница невелика, и использование статической переменной является основным вариантом.
  • Вместо метода configure(), используемого в Hibernate для загрузки конфигурации из файла, вы также можете сконфигурировать его программно, используя метод Configuration#setProperties(), но чаще используется более удобный путь — экстернализация свойств Hibernate в файл конфигурации.

    Соглашения OpenJPA

    Конфигурирование времени исполнения OpenJPA:

    • Определите статическую переменную типа EntityManagerFactory.
    • Вызовите Persistence#createEntityManagerFactory()
    Листинг 2. Конфигурация времени исполнения OpenJPA

    Подобно Hibernate, статический экземпляр EntityManagerFactory может использоваться всеми потоками JVM, обслуживающими клиентские запросы. Вы также можете определить статическую переменную типа Map, если надо использовать несколько экземпляров.

    В методе createEntityManagerFactory() выполняется поиск файла persistence.xml в папке META-INF, содержащем конфигурацию модуля хранения данных, имя которого совпадает с параметром, переданным в метод. Если файл persistence.xml найден и имя описанного в нем модуля хранения данных совпадает с указанным, то метод createEntityManagerFactory() конфигурирует экземпляр EntityManagerFactory метаданными из этого файла. Если соответствующего условиям поиска файла persistence.xml не найдено — возбуждается исключение javax.persistence.PersistenceException.

    3. Управление сессиями

    Обычно приложения получают объект сессии из SessionFactory при обработке пользовательского запроса и закрывают сессию по окончании обработки запроса; в качестве объекта клиентского запроса может использоваться HttpRequest, вызов сессионного EJB и так далее. Сессии предоставляют методы для работы с транзакциями, загрузки и сохранения сущностей в базе данных.

    Приложения Hibernate, как правило, управляют транзакциями. Для этого они часто ассоциируют сессии с потоками, так что можно не передавать сессии в виде параметров во все методы, где требуется доступ к ним; вместо этого сессии берутся из локального объекта, хранящего потоки обработки запросов. Hibernate 3.0.1 также предоставляет метод getCurrentSession(), но чаще используется внешнее управление сессиями.

    В том, что касается исключений, Hibernate 3.0 возбуждает непроверяемые исключения времени исполнения (то же справедливо и для исключений OpenJPA), следовательно — большинство приложений не содержат исключений Hibernate в сигнатурах собственных методов; также они и не обрабатывают в них исключений Hibernate. Разумеется, вы можете перехватывать и обрабатывать исключения, если это все-таки требуется.

    При этом большинство существующих унаследованных приложений на Hibernate реализованы с использованием Java SE 1.4, в то время как OpenJPA-приложения реализуются на Java SE 5.

    Ниже приведены примеры использования метода getSessionFactory() для получения фабрики сессий (или фабрики менеджера сущностей), это требуется для создания/открытия сессии (или менеджера сущностей). (Подробную информацию о методе getSessionFactory() см. в разделе Конфигурация времени исполнения)

    Соглашения Hibernate

    Управление сессиями в Hibernate:

    • Получите текущую сессию с помощью ThreadLocal.
    • Вызовите SessionFactory#openSession() для открытия сессии.
    • Вызовите Session#isOpen() и Session#close() для закрытия сессии.
    Листинг 3. Управление сессией Hibernate

    Соглашения OpenJPA


    Для управления классом менеджера сущностей EntityManager в OpenJPA :

    • Вызовите ThreadLocal для получения текущего менеджера сущностей.
    • Вызовите EntityManagerFactory#createEntityManager() для открытия сессии.
    • Вызовите EntityManager#isOpen() и EntityManager#close() для закрытия сессии.
    Листинг 4. Управление сессией в OpenJPA

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

    Приложения, использующие Hibernate, могут запускаться в различных средах с разными настройками транзакционной стратегии. Приложения могут использовать как локальные источники данных JDBC, так и транзакции Java Transaction API (JTA).

    Наиболее часто встречается использование локальных JDBC-транзакций. JTA-транзакции полезны, если вы вынуждены использовать разные источники данных, например, базу данных и очередь сообщений; JTA позволяет использовать их совместно в единой транзакции.

    Приложения Hibernate управляют собственными транзакциями через вызов методов API транзакций. Стратегия использования транзакций (JDBC или JTA) указывается в конфигурационном файле Hibernate и прозрачна для приложения.

    Соглашения Hibernate

    Для управления транзакциями в Hibernate:

    • Вызовите Session#beginTransaction() для запуска транзакции.
    • Вызовите Transaction#commit() для успешного завершения транзакции и записи изменений.
    • Проверьте активность транзакции с помощью Transaction#isActive и вызовите Transaction#rollback() для отката транзакции.
    Листинг 5. Управление транзакцией в Hibernate

    Соглашения OpenJPA

    В OpenJPA для управления транзакциями аналогично:

    • Вызовите EntityManager#getTransaction() и EntityTransaction#begin().
    • Вызовите EntityTransaction#commit().
    • Используйте EntityTransaction#isActive() для проверки активности транзакции и EntityTransaction#rollback() для отката.
    Листинг 6. Управление транзакцией в OpenJPA

    Несмотря на приведенный пример управления транзакциями в OpenJPA посредством ThreadLocal, более часто используется вызов метода getCurrentSession().getTransaction().begin(), а затем вызов getCurrentSession().getTransaction().commit(). Таким образом, в OpenJPA вам на самом деле не обязательно сохранять транзакцию в ThreadLocal.

    5. Управление сущностями

    Основные сценарии управления сущностями включают:

    • Создание хранимого объекта.
    • Получение хранимого объекта по первичному ключу.
    • Изменение хранимого объекта.
    • Удаление хранимого объекта.

    Как правило, приведенный список операций выполняется над отсоединенными сущностями, но они могут выполняться и для сущностей, связанных с базой данных.

    Соглашения Hibernate

    Для управления сущностями в Hibernate:

    • Вызовите Session#save для создания хранимого объекта в памяти.
    • Вызовите Session#load для получения хранимого объекта.
    • Вызовите Session#update для изменения хранимого объекта в памяти. Если вы изменили состояние подключенного хранимого объекта, то вызов update не требуется; Hibernate автоматически применит изменения к данным в СУБД при вызове commit().
    • Вызовите Session#delete для удаления хранимого объекта.
    Листинг 7. Управление сущностью в Hibernate

    Соглашения OpenJPA

    В OpenJPA для аналогичных действий:

    • Вызовите EntityManager#persist для создания хранимого объекта в памяти.
    • Вызовите EntityManager#find для получения хранимого объекта.
    • Вызовите EntityManager#merge для изменения хранимого объекта в памяти. Если вы изменили состояние подключенного хранимого объекта, то вызов update не требуется; OpenJPA автоматически применит изменения к СУБД при вызове commit().
    • Вызовите EntityManager#remove для удаления хранимого объекта.
    Листинг 8. Управление сущностью в OpenJPA

    В этом примере сигнатура метода ORMHelper#update() была изменена, поскольку метод update() Hibernate копирует новый объект в отсоединенный объект, который передавался в качестве параметра метода, так что метод не имеет возвращаемого значения. Соответственно, в OpenJPA метод merge() не изменяет переданный объект, а возвращает новый, созданный им объект.

    Дополнительно к изменению сигнатуры метода ORMHelper#update в унаследованных приложениях, вызывающих его, необходимо присвоить оригинальному объекту возвращенное методом значение.

    6. Отсоединенные сущности

    Еще один вариант использования Hibernate в приложениях основан на многоуровневой архитектуре и использует сессионные EJB в качестве фасада, возвращающего отсоединенные сущности Web-слою. В этом случае транзакции стартуют и завершаются в сессионном EJB при вызове его методов из Web-слоя.

    Преимуществами такого шаблона многоуровневой архитектуры является то, что, поскольку уровень EJB начинает и заканчивает транзакции внутри транзакции пользователя, транзакции изменения данных никогда не остаются открытыми, когда пользователь выполняет какое-либо действие. В результате все транзакции являются короткоживущими и должны завершаться через считанные секунды после старта.

    Большинство приложений Hibernate используют для реализации слоя EJB спецификации EJB 2.1, в то время как приложения OpenJPA используют EJB 3.0. В первую очередь вам надо перейти на сессионные EJB версии 3.0, используя в качестве ресурса локальный менеджер сущностей, поскольку это не потребует изменять логику транзакций, но вы можете продолжать использовать c OpenJPA и сессионные EJB 2.1 (см. Использование OpenJPA в WebSphere Application Server V6.1). После окончания миграции надо произвести рефакторинг приложения на EJB 3.0, используя менеджер сущностей JTA.

    Еще одно замечание по отсоединенным сущностям: если вы получили объект в одной транзакции и модифицировали его извне транзакции, вам придется вызвать update для этого объекта, чтобы сохранить изменения в СУБД. Это стандартная практика программирования в Hibernate, так же как и в OpenJPA. Аналогично, если вы получили объект из базы данных и изменили его в той же самой транзакции, то нет никакой необходимости вызывать update; как только вы завершите транзакцию, изменения в объекте будут записаны автоматически. Это также обычная практика использования и Hibernate, и OpenJPA.

    Соглашения Hibernate

    Для использования в Hibernate отсоединенных сущностей с помощью EJB 2.1:

    • Используйте шаблон сессионного фасада для оборачивания доступа к сущностям.
    • Возвращайте отсоединенные сущности (в виде Java-объектов, POJO) на Web-уровень.
    Листинг 9. Отсоединенные сущности и EJB2.1 в Hibernate

    Соглашения OpenJPA

    Для использования отсоединенных сущностей и EJB 3.0 в OpenJPA:

    • Используйте шаблон сессионного фасада для инкапсуляции доступа к сущностям.
    • Возвращайте отсоединенные сущности (в виде Java-объектов, POJO) на Web-уровень.
    Листинг 10. Отсоединенные сущности и EJB 3.0 в OpenJPA

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

    Для сравнения, класс SessionFacade, реализованный в стандарте EJB 3.0, не расширяет класс SessionBean и не реализует методы обратного вызова SessionBean (setSessionContext, ejbCreate, ejbRemove, ejbActivate, ejbPassivate). Кроме того, интерфейсы компонента, домашние интерфейсы и дескрипторы развертывания в EJB 3.0 также не требуются. Значения, указанные в дескрипторе развертывания EJB 2.1, включены непосредственно в класс SessionFacade EJB 3.0 с помощью аннотаций Java 5.

    Данный пример также демонстрирует отсутствие необходимости изменять бизнес-методы SessionFacade, как вы могли бы ожидать, изучая разницу между Hibernate и OpenJPA. Это было достигнуто благодаря инкапсуляции большинства функционала Hibernate/OpenJPA во вспомогательном классе, который уже использовался сессионным компонентом. Разумеется, ваше приложение может быть не настолько структурировано, чтобы использовать единый вспомогательный класс, оборачивающий функционал Hibernate/OpenJPA, но если вы сравните шаг за шагом изменения, сделанные во вспомогательном классе в предыдущих разделах, то сможете определить, что именно вам требуется переписать в сессионном EJB-фасаде и механизмах управления сущностями и транзакциями.

    Перенос объектно-реляционного отображения Hibernate

    Мэппинг объектов на реляционную СУБД в Hibernate может быть определен в виде набора XML-файлов, загружающихся при старте приложения. Эти файлы правил мэппинга могут формироваться напрямую или генерироваться из javadoc-подобных комментариев, встроенных в ваш исходный код. В более поздних версиях Hibernate вы также можете определять правила отображения с использованием аннотаций Java 5.

    Мэппинг объектов в OpenJPA может определяться либо набором XML-файлов, либо посредством аннотаций Java 5, напрямую встраиваемых в исходный код, что полностью устраняет необходимость отдельных файлов мэппинга.

    Большинство унаследованных приложений Hibernate используют XML-файлы мэппинга, в то время как стандартной практикой OpenJPA-приложений является аннотирование в стиле Java 5 во время разработки, но в производственном коде на основе аннотаций генерируются файлы XML, так что для переноса приложений не потребуется переписывать исходный код и перекомпилировать классы.

    Так как XML-формат является общим для Hibernate и производственного режима OpenJPA, в этом разделе мы используем именно XML для иллюстрации переноса объектно-реляционного мэппинга. Чтобы вам было легче понять, какие изменения требуются (или не требуются) в объектной модели (на основе POJO), соответствующий базовый код (за исключением аннотаций) также приведен.

    Если ваше унаследованное приложение Hibernate не использует файлы мэппинга (например, применяются javadoc-аннотации или аннотации Java 5), вы все равно сможете определить требуемые изменения для переноса приложения на OpenJPA, основываясь на информации из этого раздела. Кроме того, если вы хотите использовать аннотации Java 5 вместе с OpenJPA, соответствующие примеры приведены в Приложении.

    Также учтите, что есть несколько различных вариантов описания отображения объектов Java на реляционные таблицы. В разделе рассматриваются основные сценарии, которые могут вам встретиться в реальных приложениях, включая:

    1. Наследование

    Модель данных для промышленных приложений обычно имеет несколько мест, в которых производится обобщение и специализация сущностей, что позволяет значительно уменьшить количество одинаковых участков кода. И Hibernate, и OpenJPA поддерживают три разных варианта наследования, которые могут использоваться для модели данных на основе реляционных таблиц. Мы обсудим два из них, по нашему мнению — наиболее распространенные:

    Третий вариант (отдельная таблица на каждый класс), хоть и реже употребляется, однако поддерживается Hibernate и в виде опции реализован в провайдерах JPA, таких как OpenJPA.

    a. Наследование в рамках одной таблицы

    Для случаев, в которых базовый Java-класс содержит большинство атрибутов, необходимых расширяющим его классам, наследование может быть использовано в рамках одной таблицы с помощью поля таблицы, определяющего, какой именно подкласс базового типа содержится в этой строке. Если какие-то поля таблицы не отображены в соответствующем подклассе, такие поля должны поддерживать возможность хранения null, так как при записи объекта в таблицу их значения не будут заданы.

    Недостатком такого вида наследования является то, что если ваши подклассы определяют несколько свойств, которые должны быть непустыми для объекта, то потеря возможности задать ограничение на null может породить проблемы с целостностью данных. Основным же преимуществом такого наследования является максимально эффективная поддержка для полиморфных отношений между сущностями и запросами, выстроенных на иерархии классов, так как сложные объединения не применяются.

      Объектная модель

    Отображение объектов на таблицы 1. Наследование в рамках одной таблицы (объекты POJO)

    Соглашения Hibernate

    Для использования наследования в рамках одной таблицы в Hibernate:

    • Создайте базовый класс с полем, в котором указывается идентификатор конкретного подкласса, сохраненного в строке таблицы; кроме того, вам надо определить мэппинг первичного ключа и других свойствами (объясняется ниже и в приведенном примере не указано).
    • Создайте подкласс, в котором указывается идентификатор; также задайте мэппинг свойств, определенных именно в подклассе. Вам не придется указывать поле ключа >

    Соглашения OpenJPA

    Для использования наследования в рамках одной таблицы в OpenJPA:

    • Укажите стратегию наследования SINGLE_TABLE и определите поле, в котором хранится идентификатор класса, содержащегося в строке, в базовом классе; также определите хранимые свойства базового класса и его уникальный >


    b. Наследование объединением

    Если базовый класс не содержит большинства атрибутов, применяемых в подклассе, используйте одну базовую таблицу, которая хранит атрибуты базового класса, и отдельные присоединяемые таблицы для каждого подкласса. Присоединяемая таблица содержит поля только для тех свойств, которые не унаследованы из базового класса. Таким образом, чтение экземпляра подкласса требует операции соединения базовой таблицы с таблицей, используемой для подкласса.

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

      Объектная модель

    Отображение объектов на таблицы 4. Наследование объединением (объекты POJO)

    Соглашения Hibernate

    Для использования наследования объединением в Hibernate:

      Определите в базовом классе атрибуты первичного ключа ( >

    Соглашения OpenJPA

    Для использования наследования объединением в OpenJPA:

    • Объявите в базовом классе стратегию наследования JOINED. В базовом классе также определяется первичный ключ, применяемый для объединения всеми подклассами, опционально можно добавить поле версии. Определите отображение на таблицу прочих свойств базового класса.
    • В подклассах объявите сохраняемые свойства; таблицы, в которых будут сохраняться эти свойства, также содержат первичный ключ, используемый для объединения таблиц подкласса и базового класса. В подклассах поле версии не определяется.
    Отображение объектов на таблицы 6. Наследование соединением (XML-файлы мэппинга OpenJPA)

    2. Отношения

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

    Отношения объектной модели реализуются в модели данных путем использования ссылочного ключа в одной таблице, указывающего на первичный ключ другой. Таблица, в которой находится ссылочный ключ, является дочерней. Ее строки представляют собой объекты, чье время жизни зависит от объекта, на который они ссылаются. Таким образом, эта таблица хранит ссылочный ключ на родительский объект. Так как дочерние объекты содержат ссылочный ключ, необходимо, чтобы он всегда указывал на действительный родительский объект; он не может отсутствовать, а значит — для удаления родительского объекта необходимо предварительно удалить все его дочерние объекты либо каскадно удалить родительский объект и дочерние.

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

    Отношения между объектами разделяются на следующие категории:

    a. Отношение «один-к-одному»

    Отношение «один-к-одному» определяет ссылку одного объекта на другой хранимый объект, время жизни дочернего объекта полностью зависит от времени жизни родительского.

    Отношение «один-к-одному» является исключительным случаем. Если оно все-таки возникает, то общей практикой при использовании Hibernate для моделирования является прямое отображение всех свойств дочернего объекта на поля таблицы родительского объекта, что устраняет необходимость операций объединения таблиц для чтения свойств дочернего объекта.

    Существует также два других пути для реализации отношения «один-к-одному» в Hibernate:

    • Использование тега many-to-one с ссылочным ключом для связи с между таблицами; см. Отношение «многие-к-одному».
    • Использование тега one-to-one с первичным ключом для связи между таблицами;

    Напомним, что данный раздел описывает миграцию отношений «один-к-одному» с Hibernate. Ниже приведен пример переноса такого отношения во встраиваемые объекты OpenJPA.

      Объектная модель

    Отображение объектов на таблицы 7. Отношение «один-к-одному» (объекты POJO)

    Соглашения Hibernate

    Для использования отношения «один-к-одному» в Hibernate:

      Укажите класс, являющийся родительским; определите в его теге также первичный ключ ( >

    Соглашения OpenJPA

    Для использования отношения «один-к-одному» с помощью встраиваемых (embedded) объектов в OpenJPA:

    • Объявите встроенное поле в родительской сущности (например, Employee). Встроенные поля отображаются как часть строки таблицы, хранящей родительскую сущность и скорее должны быть составной частью сущности, чем представлять отношение к дочерней сущности.
    • Объявите дочернюю сущность (например, EmployeeRecord), как встраиваемую.
    Отображение объектов на таблицы 9. Отношение «один-к-одному» с помощью встраиваемых (embedded) объектов (XML-файлы мэппинга OpenJPA)

    b. Отношение «многие-к-одному»

    Отношение «многие-к-одному» определяется как ссылка на одиночный хранимый объект. Однако отношения вида «многие-к-одному» могут быть ненаправленными; в таком случае они чаще именуются в обратном порядке, как двунаправленные отношения «один-ко-многим».

    Отличие состоит в том, что сущность, декларируемая в отношении «многие-к-одному», является дочерним объектом (или собственником отношений), и в его таблице содержится ссылочный ключ, тогда как объект, на который ссылается сущность в отношении «многие-к-одному» — это родительский объект. Следовательно, в его таблице отсутствует дочерний ключ, а объект не является собственником отношения.

      Объектная модель

    Отображение объектов на таблицы 10. Отношение «многие-к-одному» (объекты POJO)

    Соглашения Hibernate

    Для использования отношения «многие-к-одному» в Hibernate:

    • Используйте элемент many-to-one для дочернего класса.
    • Определите первичный ключ в родительском классе.
    Отображение объектов на таблицы 11. Отношения «многие-к-одному» (XML-файлы мэппинга Hibernate)

    Соглашения OpenJPA

    Для использования отношения «многие-к-одному» в OpenJPA:

    • Определите в родительской сущности первичный ключ ( >

    c. Отношение «один-ко-многим»

    Отношение «один-ко-многим» определяется как список ссылок на сущности. Это наиболее часто используемый тип отношений, поскольку типичным случаем использования отношений между объектами является обход списка дочерних сущностей из родительского, в то время как обратный процесс — доступ к родительской сущности из дочерней — может и не требоваться; соответственно, ненаправленное отношение «один-ко-многим» является достаточным для большинства вариантов использования.

    Если требуется также использование обхода в обратном направлении, от детей к родителям, вы можете просто добавить отношение «многие-к-одному» в дочерний класс для превращения отношения в двунаправленное.

    Сущность, в которой декларируется отношение «один-ко-многим», является родительским объектом (и не является собственником отношений). Таблица, в которой хранится эта сущность, содержит первичный ключ, но не ссылочный, который хранится в таблице дочерней сущности.

    Объекты, на которые ссылаются через отношение «один-ко-многим», являются дочерними, а также владельцами отношения. В дочернем объекте содержится ссылочный ключ, указывающий на первичный ключ родительской сущности.

    В Hibernate отношение «один-ко-многим» реализуется, как правило, добавлением поля под ссылочный ключ в таблицу дочерней сущности, но настройки в файле отображения объектов на таблицы могут различаться в зависимости от того, является ли отношение одно- или двунаправленным.

    В случае использования однонаправленного отношения поле ссылочного ключа в дочерней таблице не отображается на соответствующее свойство дочернего объекта; оно есть в модели данных, но отсутствует в объектной модели. Так как отношение однонаправленное, ссылка есть в родительском объекте, но не в дочернем. Кроме того, поле ссылочного ключа должно быть определено как «nullable», поскольку Hibernate сначала создает строки дочерних сущностей (с NULL в качестве ссылочного ключа) и только потом изменяет их.

    В случае двунаправленного отношения мэппинг выглядит лучше, поскольку соответствующее свойство для поля ссылки на родительскую сущность добавляется и в дочерний объект; дополнительным преимуществом является то, что поле ссылочного ключа в БД уже может быть объявлено обязательным к заполнению. Но результирующая объектная модель в итоге содержит циклическую зависимость и связь между объектами выглядит более тесной, кроме того — требуется дополнительное программирование для заполнения обеих составляющих отношения.

    Как вы видите, существует несколько альтернатив реализации отношения «один-ко-многим», но использование однонаправленного варианта в целом более желательно, несмотря на то, что существуют случаи необходимости использования навигации и от дочернего объекта к родительскому.

      Объектная модель

    Отображение объектов на таблицы 13. Отношение «один-ко-многим» (объекты POJO)

    Соглашения Hibernate

    Для использования отношения «один-ко-многим» (однонаправленного) в Hibernate:

    • Добавьте элемент set, bag или list внутрь подэлемента one-to-many в описании родительского класса.
    • Создайте ссылочный ключ в таблице дочерней сущности, если отношение однонаправленное; в противном случае, используйте отношение «многие-к-одному» для создания двунаправленного отношения.
    Отображение объектов на таблицы 14. Отношение «один-ко-многим» (XML-файлы мэппинга Hibernate)

    Важное замечание по использованию специального выражения cascade=»all-delete-orphan» в Hibernate (см. Листинг 14): применение этого атрибута позволяет вам удалять дочерние сущности из таблицы СУБД просто удалением их из коллекции в родительском объекте и сохранением родительского объекта в СУБД. С использованием этой возможности отпадает необходимость дополнительных удалений дочерних объектов в коде программы. Хотя подобный функционал отсутствует в стандарте JPA, в реализации OpenJPA добавлена аннотация @ElementDependent, которая также поддерживает автоматическое удаление дочерних сущностей из СУБД, но данное дополнение не является портируемым на другие реализации JPA, что может привести к путанице. Далее атрибут all-delete-orphan будет рассмотрен подробно.

    Соглашения OpenJPA

    В OpenJPA вы не можете реализовать однонаправленное соединение «один-ко-многим» через ссылочный ключ; вам придется использовать соединение таблиц. Однако вы можете использовать ссылочный ключ для реализации двунаправленного отношения. Таким образом, чтобы перенести ненаправленное отношение «один-ко-многим» из Hibernate есть два пути:

    1. Используйте отображение соединением таблиц и добавьте таблицу соединения в СУБД, либо
    2. Используйте отображение ссылочным ключом и конвертируйте отношение из однонаправленного в двунаправленное, добавив соответствующее свойство дочерней сущности в объектную модель и соответственно изменив код.

    Рекомендуется конвертирование в двунаправленное отношение, поскольку обычно легче изменить код, чем изменить схему данных в СУБД и конвертировать в ней данные.

    Для использования отношения «один-ко-многим» (двунаправленного) в OpenJPA:

    • В родительском объекте добавьте элемент one-to-many для определения дочерних объектов, а также элемент >


    Существуют несколько дополнительных возможностей, часто использующихся при определении отношения «один-ко-многим», которые вам может потребоваться знать для миграции приложений:

    Hibernate

    inverse=»true»
    В файлах Hibernate вы можете увидеть атрибут inverse=»true», применяемый для определения двунаправленных отношений. Если это так — не беспокойтесь, поскольку эта возможность эквивалентна возможности OpenJPA ссылаться на сущность, не являющуюся собственником отношения, чья таблица не имеет ссылочного ключа. Аналогично, атрибут inverse=»false» Hibernate в OpenJPA соответствует собственнику отношений, в таблице которого содержится ссылочный ключ.

    В основном указанные нотации переносимы, за исключением случая, когда кто-то определяет в файле мэппинга Hibernate атрибут inverse=»true» в двунаправленном отношении «многие-к-одному». Если вы столкнетесь с таким вариантом использования, лучше поменять его до выполнения миграции, так как это применение не является лучшим образцом использования Hibernate и не позволяет генерировать оптимальные операторы SQL. Например, если в отношении «многие-к-одному» установлен атрибут inverse=»true», то каждый раз при создании дочерней сущности Hibernate выполняет два SQL-запроса, один — для создания собственно сущности, и второй — для добавления в нее ссылочного ключа на родительский объект. Изменив значение на inverse=»false» в отношении «многие-к-одному» и установив, соответственно, inverse=»true» в отношении «один-ко-многим», мы исправим этот недостаток и приведем в соответствие с моделью OpenJPA. Разумеется, вам надо будет протестировать приложение после выполнения этих изменений .

    cascade=»all-delete-orphan»
    Данному атрибуту Hibernate эквивалентов в стандартной спецификации JPA нет, однако в реализации OpenJPA добавлена аннотация @ElementDependent, которая выполняет ту же самую функцию, что и all-delete-orphan в Hibernate. Если вы хотите использовать эту возможность — смотрите подробнее в OpenJPA Users Guide. Данный функционал добавлен только в конкретной реализации OpenJPA, так что если вы портируете приложение именно на эту реализацию, то перенос функционала all-delete-orphan потребует минимальных изменений кода приложения.

    Чтобы перенести функционал all-delete-orphan совместимо со стандартом вы можете использовать атрибут cascade=CascadeType.ALL в аннотации OneToMany и изменить код приложения так, чтобы дочерний объект удалялся не только из коллекции в родительском объекте, но и собственно из СУБД; к примеру, вместо такого кода:

    должен быть заменен следующим:

    OpenJPA

    cascade=CascadeType.ALL
    Хотя все предыдущие примеры из Hibernate относились к каскадным операциям от родительского объекта к дочернему, при тестировании данного примера для OpenJPA нам пришлось определить параметр cascade=CascadeType.ALL в обоих направлениях, т.е. в как аннотации OneToMany, так и в ManyToOne. Вообще говоря, вы можете и не добавлять каскадные операции в направлении дочерний объект-родительский объект (аннотация ManyToOne), но здесь это потребовалось для работы операции merge() в направлении от родительского объекта к дочернему.

    Без определения каскадной операции в обоих направлениях каскадные remove() и persist() работали от родительского объекта Address к его зависимому объекту Phone, но каскадная операция merge() возбуждала исключение, сигнализирующее о том, что поле Phone.address в зависимом объекте не допускает каскадных операций.

    (Это хорошо известная проблема OpenJPA v0.9.7, над которой сейчас работают — исправление ожидается в следующих версиях. До выхода исправленной версии используйте обходной путь включения двунаправленных каскадных операций.)

    d. Отношение «многие-ко-многим»

    Отношение «многие-ко-многим» используется для ссылки на набор объектов через специальную промежуточную таблицу. Отношения «многие-ко-многим» не являются часто используемыми, но если реализуются, то почти всегда — как двунаправленные.

    Подобно прочим двунаправленным отношениям, существуют сущность-владелец и сущность, не являющаяся владельцем. В данном случае, владельцем отношений является сущность, хранящая ссылку на промежуточную таблицу вместо ссылочного ключа на собственно объект. Однако владельцем может быть также и другая сторона; не имеет значения, какую именно сторону вы выберите.

      Объектная модель

    Отображение объектов на таблицы 16. Отношение «многие-ко-многим» (объекты POJO)

    Соглашения Hibernate

    Для использования отношения «многие-ко-многим» в Hibernate:

    • Сущность, не являющаяся собственником отношений, использует элемент-коллекцию (set, bag или list) с атрибутом inverse=»true», атрибут table, а также подэлементы key и many-to-many.
    • Собственник отношения также использует элемент-коллекцию (set, bag или list) с атрибутом table, а также подэлементы key и many-to-many.
    Отображение объектов на таблицы 17. Отношение «многие-ко-многим» (XML-файлы мэппинга Hibernate)

    Соглашения OpenJPA

    Для использования отношения «многие-ко-многим» в OpenJPA:

    • В собственнике/дочернем объекте используйте элемент many-to-many и вложенный подэлемент join-table, чтобы указать, каким образом создать отношение. Кроме того, добавьте элемент >

    3. Поздняя (LAZY) инициализация

    Поздняя инициализация может использоваться для любого поля, но в основном она применяется в отношениях «один-ко-многим» или «многие-ко-многим»; вы можете определять, надо ли сразу загружать из СУБД все дочерние объекты при чтении родительского.

    Для более подробного объяснения идеи поздней инициализации предположим, что у нас есть отношение «один-ко-многим» или «многие-ко-многим» между сущностями A и B. Если инициализация выставлена в LAZY, то при чтении сущности A из СУБД дочерние сущности B не будут прочитаны и созданы до того момента, пока код приложения не начнет обходить их напрямую. С другой стороны, если инициализация выставлена в EAGER, то при чтении сущности A из БД все дочерние сущности будут также прочитаны и созданы в памяти.

    Будьте осторожны с использованием в декларациях отношений LAZY или EAGER, особенно с таким широко распространенным шаблоном программирования, когда EJB-слой возвращает отсоединенные объекты для вывода их Web-слоем, так как в таком случае Web-слой обходит дочерние сущности для вывода данных. С поздней инициализацией объектов Web-слой может и не получить дочерних объектов, необходимых для вывода страницы. С другой стороны, вы не можете просто указать для всех отношений тип инициализации EAGER, поскольку в таком случае каждый запрос объекта будет возвращать значительно больше информации, чем реально необходимо.

    В итоге вам придется подбирать набор стратегий инициализации в вашей модели в соответствии с требованиям бизнеса. Если бизнес-процесс требует наличия дочерних объектов в загруженном родительском — используйте EAGER. Если дочерние объекты, напротив, не нужны — указывайте LAZY.

      Объектная модель

    Отображение объектов на таблицы 19. Поздняя инициализация (объекты POJO)

    Соглашения Hibernate

    В Hibernate по умолчанию используется поздняя инициализация для отношений «один-ко-многим» или «многие-ко-многим». Для отключения поздней (и включения ранней) инициализации:

    • В родительском объекте добавьте в элемент-коллекцию (set, bag или list) атрибут lazy=false.
    • В дочернем объекте добавьте в элемент >

    Соглашения OpenJPA

    В OpenJPA поздняя инициализация для отношений «один-ко-многим» и «многие-ко-многим» также по умолчанию включена. Для отключения поздней инициализации (и включения ранней):

    • Добавьте атрибут fetch=FetchType.EAGER в элемент collection родительской сущности.
    Отображение объектов на таблицы 21. Поздняя инициализация (XML-файлы мэппинга OpenJPA)

    Разница между Hibernate и OpenJPA в доступе к дочерним сущностям отсоединенных объектов при поздней инициализации невелика. Hibernate при попытке к неинициализированному дочернему объекту генерирует исключение, в то время как OpenJPA просто возвращает null, без выбрасывания исключений.

    Причиной этой разницы является то, что спецификация JPA не указывает, как конкретно обрабатывать обращения к коллекциям дочерних объектов с поздней инициализацией в отсоединенных объектах. Каждый поставщик JPA-реализации может самостоятельно определять поведение в таком случае. Провайдеры могут возбуждать исключение либо просто оставлять объект неинициализированным, или даже возвращать пустую коллекцию.

    Поэтому, если унаследованное приложение Hibernate использует исключения для проверки наличия коллекций объектов с поздней инициализацией, вы сможете сделать то же самое при миграции на OpenJPA, проверяя возвращенное значение на null. Однако помните, что спецификация JPA не указывает, возбуждать ли исключение или нет, так что можете быть уверены — это поведение способно поменяться в следующих реализациях — возможно, даже сломав тем самым ваше приложение.

    Кроме того, также важно иметь в виду, что получили бы вы исключение (как в Hibernate) или нет (как в OpenJPA), это является показателем ошибки в приложении, которая должна быть определена и исправлена в ходе тестирования. Другими словами — это проблема независимо от того, было ли сгенерировано исключение или нет, и эта проблема должна быть исправлена. Спецификация JPA не является панацеей: ваше приложение требует архитектурных изменений, чтобы работать с отсоединенными сущностями.

    В разделах выше описывалось стандартное поведение JPA согласно спецификации, но реализация OpenJPA предоставляет дополнительные возможности через конфигурационное свойство openjpa.DetachState. Одно из свойств в конфигурации DetachState называется AccessUnloaded; если вы выставите AccessUnloaded в false, то OpenJPA будет возбуждать исключение при попытке доступа к незагруженному свойству. Данное поведение совместимо с Hibernate. Отображение 22 показывает пример конфигурации openjpa.DetachState:

    Отображение объектов на таблицы 22. Конфигурационное свойство openjpa.DetachState

    Существует три возможных решения для работы с отсоединенными объектами:

    1. Явно инициализировать все коллекции, требуемые для отображения данных, перед возвращением их Web-слою; использование поздней загрузки будет работать корректно для данного случая.
    2. Держать менеджер сущностей открытым до момента полной отрисовки данных (другими словами — вы открываете и закрываете менеджер сущностей из слоя отображения), это позволяет избежать работы с отсоединенными сущностями.
    3. Подобно предыдущему решению, третья возможность для EJB 3.0 состоит в использовании расширенного контекста хранения данных, чтобы менеджер сущностей оставался открытым между транзакциями.

    Если вы передаете сущности из слоя компонентов EJB в Web-слой, то вам необходимо следовать первому из приведенных путей решения, так как оно позволит вам сохранить транзакционную логику в EJB-слое (посредством сессионного фасада), что предпочтительнее управления транзакциями непосредственно из Web-слоя. Кроме того, управление транзакциями из Web-слоя потребовало бы завершения транзакций и закрытия соединения менеджера сущностей (скорее всего в классе ServletFilter) после отрисовки.

    В любом случае, основной отправной точкой для принятия решения о способе использования отсоединенных сущностей должно быть соответствие вашей объектной модели бизнес-требованиям. Если какие-то из сценариев работы пользователя с сайтом требуют поздней загрузки сущностей в коллекции, в то время как в остальных случаях лучше использовать раннюю загрузку — всегда используйте позднюю загрузку в вашей объектной модели, а в момент необходимости применяйте принудительную инициализацию сущностей в методах сессионного фасада. Для этого определите, в каких случаях и какие именно методы сессионного фасада используются: одни должны просто возвращать отсоединенные объекты, в то время как другие должны возвращать их отсоединенные дочерние сущности. Аналогично, есть три способа загрузить дочерние сущности в OpenJPA; первые два входят в стандарт JPA, третий является нестандартным расширением OpenJPA:

    1. Триггерная загрузка коллекции при вызове метода коллекции size().
    2. Использование выражений JP-QL с использованием параметра Fetch Join, который временно переопределяет позднюю загрузку.
    3. Использование аннотации OpenJPA — FetchGroups — для загрузки дочерних объектов.

    4. Идентификация объектов

    Все таблицы модели данных содержат поле ключа, однозначно определяющее объект, хранящийся в таблице; данное поле именуется OID и является первичным ключом таблицы. Наиболее распространенным и эффективным вариантом является использование в качестве OID целочисленного поля, значение которого задается системой. В таком случае OID отображается на объектную модель как поле, имеющее тип java.lang.Long. Все отношения между таблицами (ссылочные ключи) также основаны на использовании OID-полей в присоединяемых таблицах.

    Значение ID присваивается Hibernate при вставке каждой новой строки с помощью стандартного функционала Hibernate — генератора последовательности, класса Sequence, который использует поддержку генерации последовательностей базой данных. Данный функционал реализуется посредством использования таблицы SEQUENCE, в которой для каждой таблицы, требующей генерации уникального ключа, добавляется строка. Таким образом, OID-ы уникальны в рамках каждой таблицы, но при этом могут совпадать в разных таблицах.

    Использование в качестве OID сгенерированных ключей не несут смысла для бизнес-логики. Эти сгенерированные системой ключи в отличие от натуральных ключей (наименований и т.п.) являются синтетическими и не содержат иной информации. Использование этих синтетических ключей позволяет изменять любые бизнес-данные в базе, не нарушая ее целостности.

    В существующей модели Hibernate вы также часто можете встретить таблицы, использующие в качестве ключей осмысленные бизнес-значения, в том числе и состоящие из сочетания нескольких полей. Кроме того, вы можете встретить ограничения на уникальность значений, определенные для таких значений, играющих роль первичных ключей в модели данных. Эти ограничения на уровне СУБД гарантируют уникальность и формируют индексы для быстрого поиска сущностей бизнес-методами.

    При переносе приложения вы могли бы столкнуться со сценариями, в которых данные должны быть уже внесены в таблицы перед пользовательскими данными (например, предопределенные коды и т.п.). Чтобы реализовать такую потребность данные вставляются до первого запуска приложения, а OID-ы для подобных предопределенных строк должны быть меньше, чем стартовое число последовательности в таблице SEQUENCE для соответствующей таблицы модели данных. Соответственно, вы можете столкнуться с тем, что некоторые последовательности начинаются с ключей, больших 1.

      Объектная модель

    Отображение объектов на таблицы 23. Идентификация объектов (объекты POJO)

    Соглашения Hibernate

    Для использования уникальных ключей в Hibernate:

    Соглашения OpenJPA

    Для использования уникальных ключей в OpenJPA:

    Для Hibernate и OpenJPA DDL, создающий таблицы, использующие OID, выглядит так:

    Еще одно замечание напоследок: все ссылочные ключи в модели данных в основном базируются на первичных OID-ключах в связанных таблицах. Все ссылочные ключи должны контролироваться ограничениями целостности на уровне модели данных с помощью семантики ON DELETE RESTRICT, тогда СУБД сможет предотвратить удаление из базы данных строк, на которые есть ссылки (например, вы не сможете по ошибке удалить родительский объект).

    5. Оптимистическая блокировка

    Hibernate предоставляет возможности оптимистической и пессимистической блокировок данных в модели для контроля совместного доступа к данным. Для короткоживущих транзакций и лучшей производительности в Hibernate в основном применяется оптимистическая блокировка. Короткоживущие транзакции не блокируют объекты на время их редактирования пользователем в графическом интерфейсе. Оптимистическая блокировка заключается в проверке времени последнего изменения объекта или сравнения его версии с той, которая была в редактировавшемся объекте. Это сравнение позволяет гарантировать, что он не был изменен кем-то еще во время редактирования. Если, однако, такое случилось — механизм оптимистической блокировки возбуждает исключение и пользователь получает извещение о том, что данный объект уже был изменен другим пользователем. В таком случае пользователь может обновить экранное представление объекта из базы данных, снова отредактировать его и записать свои изменения в СУБД. Извещение пользователя механизмом оптимистичной блокировки происходит с помощью стандартной обработки исключений.

    Объектная модель

    Если совместный доступ к данным имеет значение, в сущности добавляется поле version, которое и используется провайдером для определения конкурирующих изменений одной и той же записи. Унаследованные приложения просто рассматривают свойство version как нечто неизменное и отвечают за сохранение данного свойства от момента чтения сущности из базы данных до попытки записи сущности обратно.

    Отображение объектов на таблицы 26. Оптимистическая блокировка (объекты POJO)

    Соглашения Hibernate

    Для использования оптимистической блокировки в Hibernate:

    • Используйте элемент version для определения свойства версионности.


    Отображение объектов на таблицы 27. Оптимистическая блокировка (XML-файлы мэппинга Hibernate)

    Соглашения OpenJPA

    Для использования оптимистической блокировки в OpenJPA:

    • Используйте элемент version для определения свойства версионности.
    Отображение объектов на таблицы 28. Оптимистическая блокировка (XML-файлы мэппинга OpenJPA)

    Hibernate поддерживает как оптимистическую, так и пессимистическую блокировки, но в спецификации JPA описана концепция только оптимистической блокировки. Однако OpenJPA расширил стандарт и предоставляет возможность использования пессимистической блокировки. То есть, если вы производите миграцию унаследованного приложения Hibernate, которое использует пессимистическую блокировку, укажите следующие параметры в конфигурационном файле OpenJPA persistence.xml:

    Отображение объектов на таблицы 29. Пессимистическая блокировка (конфигурационные параметры OpenJPA)

    При использовании этих параметров каждое чтение объекта сопровождается установлением разделяемой блокировки (разрешающей чтение). Эта блокировка удерживается до окончания транзакции. Если у вас есть опыт решения проблемы взаимной блокировки (deadlock) при попытке одновременного доступа к одной и той же записи, то вы, вероятно, сочтете полезным использовать блокировку на запись ReadLockLevel (аналогичную FOR UPDATE) при чтении данных, принудительно сериализующую попытки изменения записи.

    Если вы включаете режим пессимистической блокировки с помощью конфигурационных параметров в файле persistence.xml, то данная блокировка будет использоваться во всех транзакциях. Альтернативой этому способу использования может быть программная установка режима пессимистической блокировки для отдельной транзакции с помощью класса org.apache.openjpa.persistence.FetchPlan, как показано ниже:

    Отображение объектов на таблицы 30. Пессимистическая блокировка (конфигурационные свойства для OpenJPA установлены программно)

    Перенос конфигурационных параметров Hibernate

    Основным путем конфигурирования объекта SessionFactory в Hibernate является добавление элементов

    в файл hibernate.cfg.xml и добавление этого файла в корневую директорию классов приложения. Другой, реже используемый, но аналогичный по своему действию вариант — изменение конфигурационных свойств в файле hibernate.properties.

    В OpenJPA объект EntityManagerFactory конфигурируется путем добавления элементов

    в файл persistence.xml и помещением его в папку META-INF. Элемент

    определяет провайдера доступа к базе данных, файлы, содержащие конфигурацию мэппинга, а также другие свойства — такие как соединение с СУБД и уровень журналирования. В данном случае под провайдером доступа к СУБД понимается указание конкретной реализации спецификации JPA. Его свойства конфигурируются в элементе

    . В нашем случае используется OpenJPA.

    Существует как минимум три основных конфигурационных параметра, которые вы будете использовать при миграции приложений с Hibernate на OpenJPA:

    1. Соединения с СУБД — свойства, которые указывают SessionFactory, как подключиться к базе данных.
    2. Размещение файлов мэппинга — свойства мэппинга.
    3. Журналирование — свойства, указывающие уровень журналирования и данные, которые должны писаться в журнал.

    Кроме того, существуют и другие конфигурационные параметры, с которыми вы можете столкнуться. Смотрите Ресурсы для получения более полной информации. Подробнее изучить вопрос можно с помощью документации Hibernate API для класса org.hibernate.cfg.Environment и конфигурационных свойств Hibernate и документации OpenJPA.

    1. Соединение с СУБД

    Есть три пути подключения к СУБД: использование локального JDBC-соединения (рассмотрено ниже) или использование стандартных источников данных (Data Source) J2EE (см. Ресурсы).

    Соглашения Hibernate

    Для конфигурирования JDBC-соединения в Hibernate:

    • Используйте параметр dialect.
    • Используйте параметр connection.driver_ >

    Соглашения OpenJPA

    Для конфигурирования аналогичного JDBC-соединения в OpenJPA:

    • Используйте параметр openjpa.jdbc.DBDictionary (необязательно, так как OpenJPA обычно сам определяет верный диалект с помощью свойств URL и DriverName.)
    • Используйте параметр openjpa.ConnectionDriverName.
    • Используйте параметр openjpa.ConnectionURL.
    • Используйте параметр openjpa.ConnectionUserName.
    • Используйте параметр openjpa.ConnectionPassword.
    Конфигурация 2. Соединение с СУБД в OpenJPA

    Есть еще один довольно полезный параметр, отсутствующий в примере выше — openjpa.ConnectionProperties — который позволяет использовать дополнительные свойства, передаваемые во время вызова метода connect().

    2. Размещение метаданных отображения объектов на СУБД

    Если вы используете XML-конфигурацию в Hibernate, то вы можете указывать не только конфигурационные параметры, но и расположение файлов мэппинга, применяемых для отображения объектов на таблицы СУБД. Это самый распространенный способ, поскольку он позволяет сконфигурировать SessionFactory без необходимости какого-либо программного кода для указания расположения файлов мэппинга.

    Соглашения Hibernate

    Для конфигурирования расположения файлов мэппинга в Hibernate:

    • Используйте элемент с атрибутом resource.
    Конфигурация 3. Указание расположения файлов мэппинга в Hibernate

    Соглашения OpenJPA

    Для конфигурирования расположения файлов мэппинга в OpenJPA:

    Конфигурация 4. Указание расположения файлов мэппинга в OpenJPA

    3. Журналирование

    Определение причин ошибок и их устранение в Hibernate может быть затруднительно, так как Hibernate автоматически генерирует команды SQL, используя метаданные из файлов мэппинга. Нередко бывает полезно включить запись событий в журнал, чтобы видеть генерируемые Hibernate SQL-запросы.

    Соглашения Hibernate

    Для конфигурирования свойств журналирования в Hibernate:

    • Используйте параметр show_sql для вывода SQL-команд.
    Конфигурация 5. Журналирование в Hibernate

    Однако простого чтения SQL-запросов недостаточно для диагностирования проблемы, так что существуют и другие категории данных, доступных для журналирования в Hibernate; вы можете их установить в файле log4j.properties. (Рассмотрение данного конфигурационного файла не является целью статьи, отсылаем вас к документации Hibernate для получения более полной информации о настройках журналирования в Hibernate.)

    Соглашения OpenJPA

    Для конфигурирования свойств журналирования в OpenJPA:

    • Используйте параметр openjpa.Log для вывода SQL-запросов.
    • Используйте параметр openjpa.ConnectionFactoryProperties для подробного журналирования SQL-запросов.
    Конфигурация 6. Журналирование в OpenJPA

    Также вы можете сконфигурировать OpenJPA для записи в журнал другой полезной информации, используя свойство openjpa.Log.

    Заключение

    Данная статья провела вас через основные этапы портирования приложений с Hibernate 3, использующего EJB 2.1, на новый промышленный стандарт JPA, точнее — его реализацию OpenJPA 0.9.7, использующую EJB 3.0. Миграция приложения была описана в виде набора основных сценариев, в каждом из которых пошагово производилось сравнение реализации Hibernate и OpenJPA. Такое сравнение должно помочь тем, кто портирует существующие приложения Hibernate/EJB 2.1 на OpenJPA/EJB 3.0, также как и тем, кто планирует использовать OpenJPA/EJB 3.0 в своих следующих проектах.

    Статья описывает процесс переноса на новую платформу три основных составляющих Hibernate-приложения (исходный код приложения, файлы мэппинга, параметры конфигурации) и приводит к следующим выводам:

    Если код приложения Hibernate хорошо структурирован и вызовы функционала Hibernate обернуты во вспомогательный класс, то перенос исходного кода приложения в большинстве случаев легок и очевиден. Для программ, которые не используют оборачивающих классов для вызова методов Hibernate, миграция более трудоемка, но скорее сводится к синтаксическим различиям, нежели к различиям в бизнес-логике (т.е. управлению сессиями, транзакциями и сущностями).

    Так как объектно-реляционный мэппинг в приложении уже настроен, то вам надо использовать для миграции поэтапное и одновременное изменение как модели данных, так и объектной модели. Использовать только восходящий (от данных к объектной модели) или нисходящий (от объектной модели к модели данных) процессы миграции нежелательно: в процессе миграции должна быть сохранена целостность обеих частей приложения. В этой статье используется именно такой метод портирования — поэтапно и с обеих сторон, вручную. Скорее всего, утилиты Dali JPA Tools или IBM Design Pattern Toolkit (см. Ресурсы) смогут автоматически произвести большинство действий по миграции с Hibernate XML на OpenJPA XML.

    Большинство конфигурационных параметров легко переносимы с Hibernate на OpenJPA, но кроме общих параметров есть ряд дополнительных, используемых для тонкой настройки производительности в Hibernate, часть которых может не потребоваться в OpenJPA. Соответственно не следует производить полное портирование всех настроек производительности. Вместо этого переносите основные конфигурационные параметры, а затем протестируйте изменения производительности под действием различных настроек самого OpenJPA.

    Статья не рассматривает вопрос переноса запросов Hibernate на OpenJPA, так как для приложений Hibernate характерно наличие хорошо спроектированной модели данных, соответствующей бизнес-требованиям, чем большое количество нестандартных запросов. Для тех же приложений, модель данных которых спроектирована плохо, лучше использовать решения типа iBatis, чем объектно-реляционный мэппинг в реализациях Hibernate или OpenJPA. Однако если со стороны читателей будет проявлено достаточно интереса, то, возможно, вопросы переноса запросов Hibernate на OpenJPA будут рассмотрены в следующей статье цикла.

    Hibernate/GORM: решение проблемы N+1

    Многие программисты, работающие с Hibernate или любым подобным ORM-фреймворком, рано или поздно сталкиваются с так называемой проблемой N+1.

    Столкнулась с ней и наша команда, когда реализовывала проект на Grails (популярный веб-фреймворк для Groovy). Для ORM Grails использует GORM, “под капотом” у которого все тот же старый добрый Hibernate.

    Для тех разработчиков, которые с этой проблемой еще не встречались, обрисуем вкратце ее суть. Допустим, имеется следующая вполне типичная схема:

    Есть “Новость” и у нее может быть много “Комментариев”.

    Если нам потребуется получить последние десять новостей с комментариями, то при настройках по умолчанию мы выполним одиннадцать запросов к базе данных: один для получения списка новостей и по одному запросу для каждой новости, чтобы получить ее комментарии.

    Оптимальным окажется вариант, при котором база находится на той же машине или хотя бы в той же локальной сети, а количество новостей ограничено десятью. Но, к сожалению, в реальности база будет располагаться на выделенном сервере, количество новостей на странице будет порядка 50 и подобное решение может привести к серьезным проблемам с производительностью.

    При помощи Hibernate можно найти несколько решений этой проблемы. Рассмотрим их вкратце.

    FetchMode.JOIN

    В маппинге для интересующей нас ассоциации, либо непосредственно при выполнении запроса, мы можем указать метод выборки JOIN. В этом случае необходимая ассоциация будет получена тем же запросом. Это будет работать для связей 1-1 или *-1, но для запросов 1-* мы столкнемся с определенными проблемами.


    Рассмотрим следующий запрос:

    Первая же проблема, которая бросается в глаза — limit 10 не отработает так, как нам нужно. Вместо того, чтобы вернуть первые десять новостей, этот запрос вернет первые десять записей. Количество новостей в этих десяти записях будет зависеть от количества комментариев. Если у первой новости 10+ комментариев, то мы получим только ее в результате выборки.

    Все это вынуждает Hibernate отказаться от нативных средств базы данных для ограничения (limit) и смещения (offset) выборки и обрабатывать результат запроса уже на стороне сервера приложения.

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

    Для того, чтобы это исправить, нужно выставить Result Transformer для критерия:

    Даже если отбросить все эти недостатки, то у этого метода есть и более серьезные ограничения: например, он не справляется с задачей “помимо комментариев получить еще и авторов статьи”

    FetchMode.SUBSELECT

    Другой альтернативой мог бы стать SUBSELECT. Вместо того, чтобы делать JOIN, он выполняет дополнительный запрос для связанных сущностей, используя оригинальный запрос как SUBSELECT.

    В итоге мы получаем вместо одиннадцати запросов всего два. Один основной запрос и по одному запросу на каждую ассоциацию.

    Это отличный вариант, который будет работать и в том случае, если нам нужно получить сразу и комментарии, и авторов. Но, к сожалению, тоже не лишен ограничений.

    Во-первых, использовать его возможно только на этапе описания маппинга с помощью аннотации @Fetch(FetchMode.SUBSELECT)

    Во-вторых, мы никак не можем контролировать использование этого режима (в отличии от того же JOIN) в момент выполнения запроса. Таким образом, мы не будем знать, реально ли используется этот режим или нет. Если другой разработчик поменяет маппинг, то всё может “посыпаться”: например, оптимизация просто перестанет работать и будет использоваться первоначальный вариант с 11-ю запросами. При этом, данная связь будет неявной для того, кто вносит изменение.

    В-третьих, что для нас стало решающим фактором, этот режим не поддерживается в GORM — Grails фрэймворке для работы с базой данных, построенном поверх Hibernate.

    Подробнее о возможных стратегиях выборки можно почитать по этой ссылке .

    С учетом вышесказанного, нам ничего не оставалось кроме как вооружиться IDEA, запасом свободного времени и погрузиться в недра Hibernate. Результатом стало…

    Ультимативное решение

    Если немного пофантазировать на тему идеального решения, напрашивается следующий вариант: сделать интересующую выборку, затем при необходимости за один раз подгрузить нужные коллекции.

    От фантазий к делу. Результатом изысканий стал следующий код на Groovy (при необходимости его легко можно преобразовать в Java код):

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

    Надеюсь, что данная статья будет Вам полезна и поможет писать лучший код.

    Hibernate (конфигурация в стиле аннотаций)

    В прошлом посте мы создали простое приложение которое работает с базой данных через фреймворк hibernate. В этом посте создаваемая нами программа будет делать все тоже самое, только для конфигурирования сущностных классов будем использовать не xml файлы с расширением hbm.xml, а добавим конфигурацию в сами сущностные классы в виде аннотаций. Так же в этом посте для сборки приложения мы воспользуемся мавеном, так как сборка проекта из терминала хорошо описана в предыдущем посте.

    Похожие посты

    Что нам потребуется

    1. JDK
    2. Инструмент управления зависимостями maven

    База данных

    База данных в этом посте точно такая же как в посте Работа с базой данных через JDBC. Там приводится ее структура и как ее поднять в MySQL. Для экономии места в этом посте я эти детали не привожу. Кто собирается все это дело компилировать и запускать, то убедитесь, что у вас установлена MySQL и засетаплена соответствующая база из того поста.

    Структура проекта

    Создайте мавенский проект представленной ниже структуры:

    Добавляем java код

    Добавим сущностные классы, в которые добавим соответствующие аннотации отображения.

    Warehouse.java

    Item.java

    Provider.java

    Аннотации в сущностном классе

    Теперь опишем какие аннотации за что отвечают и о чем они говорят хибернейту.

    1. Аннотация @Entity. Каждый класс в самом начале мы про аннотировали аннотацией @Entity. Эта аннотация говорит хибернейту о том, что это отображенный сущностный класс.
    2. Аннотация @Table. Аннотация @Table, следующая за ней, определяет имя таблицы в базе данных, на которую эта сущность мапится.
    3. Аннотация @Column. Каждое поле в классе (вместо этого можно его гет-метод) проаннотировано аннотацией @Column, который указывает имя столбца в таблице, на который это поле мапится.
    4. Аннотации @ >Аннотации касались только сущностных классов, далее все тоже самое, что и в предыдущем посте, кроме как тут не будет xml фалов отображения.
      Опишем dao-классы, их интерфейсы и реализацию, тут вроде все просто.

    ItemDAO.java

    ProviderDAO.java

    WarehouseDAO.java

    ItemDAOImpl.java

    ProviderDAOImpl.java

    WarehouseDAOImpl.java

    Factory.java

    Объект фабрика будет создавать и возвращать объекты dao-слоя.

    HibernateUtil.java

    Класс HibernateUtil.java создает и возвращает фабрику сессий по шаблону синглетон, гарантируя, что инстанс фабрики будет один на протяжении всего выполнения приложения.

    App.java этот класс с которого будет запускаться приложение.

    App.java

    AppTest.java

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

    В файле hibernate.cfg.xml пропишем всю необходимые хибернейту конфигурацию, какая будет использоваться база, какой логин и пароль к этой базе и пр.

    hibernate.cfg.xml

    Билд скрипт

    pom.xml

    Сборка проекта и запуск проекта

    Теперь все что надо сделать это запустить командочку mvn test и мавен соберет проект и прогонит тесты:

    После запуска тестов, мы должны увидеть полный вывод базы данных.

    EasyJava

    Java в примерах для начинающих

    Управление сущностями в Hibernate

    Написав о отображении классов в таблицы можно написать и о работе с этими классами и таблицами. Для управления сущностями Hibernate использует подход, схожий с JPA, когда каждая сущность имеет какое-то собственное состояние, а вызовами методов Hibernate это состояние изменяется, при этом одновременно изменяя и обновляя данные в JVM и в базе данных .

    Bootstrapping

    Как и всё остальное, Hibernate имеет начало и это начало называется Bootstrapping. Hibernate Boostrapping или, говоря простыми словами, запуск и конфигурирование Hibernate, выполняется в три шага.

    В первую очередь необходимо создать ServiceRegistry , которая создаёт и предоставляет сервисы, которые нужны Hibernate для старта и дальнейшей работы. Можно построить ServiceRegistry самостоятельно, а можно воспользоваться стандартной. Второй вариант, очевидно, проще:

    Вызывая различные методы класса StandardServiceRegistryBuilder можно управлять, какой будет построенная ServiceRegistry . В частности именно тут можно изменить имя файла конфигурации со значения по умолчанию hibernate.cfg.xml на своё собственное.

    Следующим шагом настраивается связь между классами и таблицами:

    Создание MetadataSources оборачивается в try/catch блок, чтобы очистить ServiceRegistry если MetadataSources не сможет создаться. В случае успеха же MetadataSource сам будет управлять жизненным циклом ServiceRegistry .

    Создать MetadatsSources можно и без ServiceRegistry , но тогда не будут автоматически прочитаны определения сущностей из конфигурации Hibernate и их придётся задавать вручную:

    В коде выше показано, как можно добавить класс по его типу, класс по его имени или определение сущности из внешнего файла.

    Наконец, последним шагом, из MetadataSources строится SessionFactory :

    Session

    Из SessionFactory , построенной при запуске Hibernate, можно получить одну или несколько сессий. Если проводить аналогию с JDBC, то SessionFactory будет аналогом DataSource , а Session — аналогом Connection . Для нас главное различие в том, что SessionFactory объект достаточно тяжёлый и его создание занимает довольно много времени. Поэтому в программе обычно используется один экземпляр SessionFactory . В тоже время объект Session весьма легковесный и конструируется быстро, поэтому их создают по мере необходимости.

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


    Контекст сессии понятие достаточно эфемерное. Один или несколько объектов Session могут образовывать persistence context. Я не буду переводить этот термин, попробую лучше его объяснить. Наличие persistence context означает, что для каждой существующей на данный момент сущности существует Session , которая с сущностью связана и следит за её состоянием. Что это значит? Смотри ниже.

    Сущности и состояния

    В Hibernate каждая сущность имеет своё состояние и может менять его подчиняясь строгим правилам перехода из состояния в состояние. Эти состояния и правила отображены на рисунке в начале статьи.

    Когда будущая сущность создаётся оператором new , она чиста и невинна и Hibernate про неё ещё не знает. Такая свежесозданная сущность считается «транзитной» (transient). Транзитная сущность не имеет связи с базой данных, не имеет данных в базе данных и, за редким исключением, не имеет id.

    Такая сущность довольно бесполезна, поэтому сразу переведём её в состояние «постоянная» (persistent):

    Метод save ( ) возвращает > save ( ) есть ещё несколько методов, способных перевести сущность из «transient» в «persistent»: saveOrUpdate ( ) позволяет не задумываться, новая это сущность или просто изменённая старая и сам выбирает, сохранить новую или обновить существующую; persist ( ) ведёт себя как save ( ) , но он не возвращает id вновь сохранённой сущности.

    В отличие от JPA, Hibernate сам (по умолчанию) не следит за изменениями в объектах и требует явного обновления сущности в базе данных, когда она меняется в коде:

    Когда объект op был сохранён вызовом save ( ) и в нём после этого были изменены данные вызовом op . setAccountId ( 9000 ) , эти изменения не попали автоматически в базу данных, даже после того как транзакция была подтверждена и закрыта. Hibernate передаёт изменения объектов в базу только явно, с помощью вызовов update ( ) или saveOrUpdate ( ) .

    Чтение из базы

    Разумеется, создание новых объектов и сохранение их в базу, это не единственный метод сделать какой-то объект «persistent» и, строго говоря, даже не самый популярный. Чаще всего объекты наоборот, читаются из базы данных с помощью методов get ( ) и load ( ) . Оба метода принимают одинаковый набор параметров и в общем случае это будет тип загружаемого класса и его id:

    Отличаются эти методы лишь поведением: get ( ) возвращает null , если объекта указанного типа и с указанным > load ( ) бросит исключение при попытке загрузить несуществующий объект. А если быть точным, то не при попытке загрузить объект, а при попытке обратиться к данным сущности. Дело в том, что load ( ) загружает данные из базы «лениво», откладывая фактическую загрузку на момент обращения к этим данным. А если к данным не обращаться, то и фактического запроса к базе тоже не произойдёт

    Ленивая загрузка

    Когда сущность стала persistent, она обретает связь с объектом Session , который её породил и включается в persistent context. Метод load ( ) возвращает на самом деле не объект запрошенного класса, а ссылку на обёртку вокруг этого класса, которая знает, как ей обратиться к Session и загрузить данные в тот момент, когда они понадобятся. Очевидно, что такая обёртка бесполезна за пределами своего peristence context.

    Однако не только load ( ) может вернуть неполный класс. Любое поле может быть помечено для ленивой загрузки аннотацией @Basic ( fetch = FetchType . LAZY ) и даже если загрузить объект с таким полем методом get ( ) , для доступа к этому полю всё равно будет требоваться наличие persistence context. Используя аннотацию @LazyGroup ( «groupname» ) можно объединять лениво загружаемые поля в группы и тогда обращение к одному полю в группе автоматически вызовет загрузку всех полей в этой группе.

    По умолчанию у любого класса, который не указывает явно, какие поля стоит загружать отложенно и как такие поля группировать, все поля которые содержат единственное значение, такие как String , BigDecimal , Long и другие, объединены в группу загрузки по умолчанию. В свою очередь поля с множественными значениями, такие как Set , List и другие, имеют собственную группу отложенной загрузки для каждого поля.

    Таким образом можно сказать, что если у вас есть какое-то поле с множественными элементами в классе и вы явно не настраивали для него ленивую загрузку, оно, скорее всего, будет загружено лениво и для доступа к элементам этого поля будет требоваться наличие persistence context у сущности.

    Detached entities

    Итак, когда сущность только создана и записана в базу данных или когда наоборот, прочитана из базы данных, она входит в persistence context и обладает неким экземпляром Session , который ей управляет. Однако из этого состояния она может внезапно перейти в состояние «отделённая» (detached). В этом состоянии сущность не связана со своим контекстом (отделена от него) и нет экземпляра Session , который бы ей управлял.

    Перейти в это состояние сущность может по следущим причинам:

    • Явный перевод из persisted в detached вызовом метода evict ( ) у Session .
    • Сброс контекста методом clear ( ) у Session .
    • Явное закрытие сессии методом close ( ) .
    • Неявное закрытие сессии связанное с удалением объекта Session .

    Над detached объектом нельзя выполнять операции, которые требуют наличия persistence context:

    Hibernate: лучшая практика, чтобы вытащить все ленивые коллекции

    Что у меня:

    Какая проблема:

    Проблема в том, что я не могу получить ленивую коллекцию после закрытия сессии. Но я также не могу закрыть сеанс в методе продолжения.

    Какое решение (грубое решение):

    a) Перед закрытием сессии заставьте спящий режим тянуть ленивые коллекции

    b) Возможно, более привлекательным способом является использование аннотации @Fetch(FetchMode.SUBSELECT)

    Вопрос:

    Какова наилучшая практика/общий способ/более гибкий способ сделать это? Средство конвертирует мой объект в JSON.

    Алексей Кутепов

    Пример использования Hibernate.

    Spring Framework — универсальный фреймворк с открытым исходным кодом для Java-платформы. Несмотря на то, что Spring Framework не обеспечивает какую-либо конкретную модель программирования, он стал широко распространённым в Java-сообществе главным образом как альтернатива и замена модели Enterprise JavaBeans. В нашем примере он будет использоваться для конфигурирования Hibernate. Если вы ранее не работали со Spring, то я так же настоятельно рекомендую с ним ознакомиться, так как на практике вам придётся довольно часто иметь с ним дело. Официальный сайт проекта http://spring.io.




    В качестве примера мы с вами разработаем простой телефонный справочник, который будет хранить список контактов в базе данных, при этом у каждого контакта будет ещё список телефонных номеров.

    В этой статье в качестве базы данных для проекта используется PostgreSQL.

    Теперь перейдём непосредственно к разработке. Создайте Maven-проект в любой удобной для вас IDE, лично я предпочитаю Idea IntelliJ, или же можно сгенерировать проект с помощью Maven командой:

    mvn archetype:generate \
    -DarchetypeGroup > -Dgroup > -Dartifact > После того как проект создан, откройте файл pom.xml и отредактируйте его чтобы он выглядел так:

    Давайте посмотрим что содержит наш pom.xml.

    Первая важная деталь, это название нашего проекта, артефакт и версия. Зная эту информацию мы можем подключать наш проект как библиотеку к другому проекту, что довольно удобно. Слово SNAPSHOT говорит о том что проект ещё в разработке, поэтому в релизной версии слово SNAPSHOT нужно убрать, оставив только номер версии, например 1.0 .

    создан для удобства. Сюда мы вынесли версии библиотек, которые используются в проекте.
    Далее в блоке у нас идёт список зависимостей для нашего проекта. В него я включил библиотеки для работы с Hibernate и Spring, а так же библиотеку для работы с базой данных PostgreSQL:

    Ну и последний блок содержит плагины, необходимые для нашего проекта:

    Хочу обратить внимание на exec-maven-plugin. Он позволяет запускать наш проект средствами Maven, что будет для нас крайне полезно. Для этого в директории проекта достаточно выполнить команду:

    Обратите внимание что мы сделали ссылку на main-класс для exec-maven-plugin, который пока что не создали: phone.book.main.Main .

    Теперь создайте в каталоге /src/main/resources/META-INF/sping файл beans.xml со следующим содержанием:

    В beans.xml мы сконфигурировали DataSource, который содержит всю необходимую информацию для подключения к базе данных: класс JDBC-драйвера (в данном случае драйвер для PostgreSQL), URL, логин и пароль.
    Затем мы создаём бин hibernateSessionFactory, используя класс LocalSessionFactoryBean из библиотеки Spring ORM. Этот класс позволяет нам сконфигурировать все настройки Hibernate в спринге, и инициализирует класс SessionFactory, с помощью которого осуществляется взаимодействие с базой данных.
    Для бина hibernateSessionFactory мы задаём проперти, которые используются для инициализации SessionFactory:

    • DataSource, без которого мы не сможем установить подключение к базе данных.
    • Классы-сущности Hibernate, в проперти «annotatedClasses«. Они представляют собой описание структуры таблиц базы данных в виде объекта. Эти классы мы скоро создадим.
    • Настройки Hibernate в проперти «hibernateProperties«. Тут 2 очень важных момента, а именно это SQLDialect (в данном случае PostrgeSQLDialect), который должен быть выбран или определён в соответствии с той базой данных, которую мы планируем использовать, и параметр «hibernate.hbm2ddl.auto» который определяет может ли Hibernate создавать таблицы или редактировать их структуру, если их нет или они отличаются о того что описано в классах-сущностях. У нас параметр «hibernate.hbm2ddl.auto» имеет значение «update«, то есть в случае отсутствия необходимых таблиц в базе данных Hibernate их создаст.

    Последним шагом мы конфигурируем в спринге класс PersonDaoImpl и передаём ему объект SessionFactory в проперти.

    Теперь в каталоге /src/main/java создайте пакет phone.book.dao и поместите туда интерфейс и класс, что описаны ниже:

    Класс PersonDaoImpl умеет всего 2 вещи: сохранять объект Person в базу данных и получать из базы данных все сохранённые объекты Person. Класс Person описывает структуру таблицы PERSON из нашей базы данных. Забегая вперёд, скажу что у нас ещё будет таблица PHONES.
    Давайте создадим классы для этих таблиц в пакете phone.book.model:

    Классы-сущности Person и Phone заслуживают особого внимания, как я уже говорил, они являются объектным представлением таблиц базы данных для фреймворка Hibernate. Любой класс-сущность должен начинаться с аннотации @Entity. Далее в коде следует аннотация @Table, содержащая в качестве параметра название таблицы, которую описывает класс-сущность. Аннотация @Id указывает на то, что данное поле является индексом, аннотация @Column определяет соответствие переменной конкретному столбцу таблицы, а аннотация @GeneratedValue указывает на то что значение этого поля будет генерироваться автоматически.
    У нас есть ещё один очень интересный момент. Дело в том что таблицы PERSON и PHONES связанны между собой связью «один ко многим», то есть у одного человека может быть несколько номеров телефона. Эта связь описана с помощью аннотации @OneToMany.

    У нас всё готово для работы с базой данных, теперь осталось написать класс, который будет использовать всё что у нас уже есть. Создайте пакет phone.book.main и поместите туда главный класс для нашего приложения:

    Тут всё довольно просто: мы получаем контекст спринга с помощью ClassPathXmlApplicationContext, достаём из контекста наш ДАО, инициализируем классы сущности с константными значениями и сохраняем информацию из этих классов в базе данных. Затем выводим на экран все контакты, которые сохранены в базе данных.

    На всякий случай выкладываю скрин с общей структурой нашего проекта:

    Чтобы увидеть результат работы, соберите проект с помощью команды:

    Hibernate — быстрый старт. Пример приложения Hibernate 5 Hello World

    Java Hibernate — настройка, подключение к БД и создание простого приложения

    Hibernate — один из самых популярных фреймворков для Java, выполняющая объектно-реляционное отображение (object-relational mapping — ORM). Этот фреймворк значительно сокращает время на реализацию задач отображения данных из таблиц в классы (POJO).

    Используемые технологии:

    IntelliJ IDEA 14

    1. Описание задачи

    Необходимо создать пустой проект maven, подключить к нему фреймворк Hibernate. Затем подключиться к базе данных и записать в таблицу данные.

    2. Структура проекта

    Здесь созданы сущности из БД, файл для создания sessionFactory и файл настроек hibernate.cfg.xml .

    3. Создание проекта

    Создаем простой maven проект. Дальше подгрузим всё через pom.xml

    4. pom.xml

    На самом деле для простейшего приложения нам бы понадобились всего две зависимости: mysql-connector-java и hibernate-core . Но т.к. остальные всё равно вам скорее всего понадобятся, то для удобства поиска в следующий раз я собрал всё в одном файле. Также обратите внимание, что при использовании hibernate-entitymanager подгружать hibernate-core и другие зависимости не нужно — всё будет найдено автоматически. Правда существуют рекомендации всё же подключать зависимости по отдельности во избежание глюков и лишней работы по отлавливанию исключения.

    5. Подключение фреймворка Hibernate в проект

    В >Add Framework Support… и там выбираем Hibernate. Если у вас подгрузились зависимости, указанные выше, то будет доступен выбор из зависимостей maven. Так же можно установить галочку Import database schema о настройке схемы базы данных (БД) сразу после нажатия на ок. Я покажу эту настройку отдельно.

    6. Создание файла настроек фрейморка hibernate.cfg.xml

    Теперь нам нужно создать главный файл настроек Hibernate. Сделать это можно несколькими способами, вот один из них:

    В настройках проекта (ctrl+alt+shift+s) выбираем хибернейт и дальше справа +. Указываем путь где будет храниться этот файл и жмем ок.

    Листинг уже с настройками показан ниже. Обратите внимание, что после автоматической генерации сущностей свойства имя пользователя и пароль перезаписываются (или вообще удаляются). Так что здесь приведен уже конечный файл:

    7. Подключение к базе данных MySQL

    Этот пункт не будет рассмотрен в этой статье, т.к. он подробно рассматривается в отдельной заметке. Здесь можно посмотреть как создать базу данных MySQL и подключиться к ней в Idea.

    8. Создание сущностей (POJO-классы) для колонок из базы данных


    После того как мы подключились к БД можно сгенерировать сущности автоматически. Для этого в >Generate Persistence Mapping — By Database Schema .

    Далее появится такое окошко. Прописываем схему базы данных, куда будут складываться классы, а также префикс и суффикс к сгенерированным названиям. В моем случае я выбрал суффикс Entity и поэтому колонка contact будет замапина в класс ContactEntity .

    Выбираем какие настройки будут сгенерированы и жмем ок. Хоть на скриншоте и выбраны все галочки, но в итоге я удалил xml, которые были сгенерированы ( Generate Separate XML per Entity — для каждой сущности будут сгенерирован свой xml файл) и оставил только классы с аннотациями (наиболее удобный и распространенный вариант, хотя вариант с xml имеет свои достоинства)

    9. Дополнительная информация по автоматическому созданию сущностей

    Всё генерится замечательно, но есть некоторые особенности, которые обязательно необходимо учитывать. В этом примере сразу всплывают несколько вещей, которые я вам хотел показать в качестве примера. Итак.

    В таблицах используется колонка с датой.
    Ее формат SQL Date . Именно так и сгенерится класс ContactEntity . Но в Java используется java.util.Date и это нужно учитывать. Для того, чтобы связать дату из sql.Date и java.util.Date необходимо заменить аннотацию @Basic на @Temporal , а так же изменить тип возвращаемого результата (после автоматической генерации был sql.Date )

    В таблице при создании была создана колонка для контроля версий. Это тоже было пропущено при создании сущностей. Для исправления применяем аннотацию @Version :

    Третий момент в этом примере связан с связью таблиц между собой. Этот вопрос будет рассмотрен в других статьях, но чтобы вы смогли запустить этот пример нужно исправить в этом методе два параметра на false , вместо true из класса ContactHobbyDetailEntityPK :

    Таким образом Вы увидели, что использование автоматического создания сущностей вещь крайне полезная, но понимать что там будет создано необходимо. На исправление уходит 1-2 минуты, а на прописывание каждого класса вручную намного больше.

    В примере будет использована только одна сущность и ниже приведён её листинг:

    10. Реализация org.hibernate.SessionFactory

    Для работы с hibernate используется реализация интерфейса SessionFactory . Отметьте себе, что она создается один раз для приложения!

    Есть несколько примеров как создается реализация sessionFactory . Здесь показана реализация из официального мануала jboss.org, с добавлением геттера и метода по завершению подключения:

    11. Запуск приложения

    Для запуска напишем простенький класс и запишем одну строку в таблицу:

    После запуска проверим что получилось прямо из среды разработки. Как видим одна строчка записалась успешно:

    Ссылки для скачивания
    Комментарии (42) для “ Hibernate — быстрый старт. Пример приложения Hibernate 5 Hello World ”

    Добрый день!
    Спасибо за статью, заработало почти с первого раза!
    Когда вставляю ваш код из class HibernateSessionFactory, то программа падает.
    Как все таки удобно работать в Idea

    Расскажите, пожалуйста, в чем ошибка и как решили. Это поможет другим читателям (или я подправлю код в исходниках, если дело в них).
    P.S. скачал исходники в конце статьи и запустил как есть, всё отработало.

    Очень интересно узнать как поправили HibernateSessionFactory. Бросает «Could not locate cfg.xml resource [hibernate.cfg.xml] «

    Скорее всего у вас идет неправильная ссылка на местонахождение файла настроек. Часто бывает что cfg лежит в папке webapp/resources, а не src/resources. В любом случае посмотрите как обращаетесь к файлу и как прописаны настройки.

    Exception in thread «main» org.hibernate.exception.SQLGrammarException: could not execute statement

    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table ‘javastudy.Contact’ doesn’t exist

    делал по вашему примеру и когда сделал 8 пункт выскочила ошибка

    Connection to hibernate.cfg.xml/Hibernate failed
    Could not create connection to database server. Attempted reconnect 3 times. Giving up.

    Из этого я понял что Hibernate не может соединится с базой данных. В чем может быть причина?

    В итоге я сделал до конца приложение. В базу данные добавляются. Но вот что странное. На вкладке DataBase появилось два типа подключения к базе. Одно через MySQL и другое через Hibernate. Через MySQL все работает. Я могу посмотреть что элементы в таблицы добавляются. А вот когда пробую сделать тоже самое через Hibernate, то не получается. Вот скриншот.

    На скрине просто две разных базы. Судя по описанию выше — приложение не видит настройки соединения или они не правильные. Вам нужно смотреть под вашу локальную базу что и как. А красное поле с ошибкой указывает на неправильную авторизацию.

    Получил Exception на выходе (

    Что может быть не так?

    Exception in thread «main» java.lang.ExceptionInInitializerError: Initial SessionFactory failedorg.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
    at utils.HibernateSessionFactory.buildSessionFactory(HibernateSessionFactory.java:25)
    at utils.HibernateSessionFactory. (HibernateSessionFactory.java:10)
    at main.AppMain.main(AppMain.java:11)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

    Ошибка происходит в этом блоке:

    Сергей, а как Вы решили проблему?
    У меня такая же ошибка.

    Отбой, с Вашим проектом все заработало.

    Появился еще вопрос: почему в моих POJO классах после их сборки прописалось?

    Честно говоря не вспомню дописывал это специально или это было сгенерировано. В любом случае, думаю, узнать что это за два пункта у вас не составит труда:)

    Подскажите, в чём может быть проблема: нажимаю на проект, add framework support — вижу только grovee and Kotlin.

    IDEA Community Edition? У автора Ultimate и в ней расширенная поддержка фреймворков.

    а что же мне делать в таком случае?

    Прописать всё руками и добавить зависимости через maven. Или подумать как многие работают с Ultimate IDEA (например можно купить).

    Поставил себе Ultimate, почти всё заработало, кроме этого:
    Exception in thread «main» java.lang.ExceptionInInitializerError: Initial SessionFactory failedorg.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
    at HibTestProject.HibernateSessionFactory.buildSessionFactory(HibernateSessionFactory.java:25)
    at HibTestProject.HibernateSessionFactory. (HibernateSessionFactory.java:10)
    at HibTestProject.AppMain.main(AppMain.java:11)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

    Не пойму, в чём проблема? Подскажите пожалуйста.

    Я тоже столкнулся с этой проблемой. Есть предположение, что это связанно с версией драйвера для MySQL. Автор использует версию 5.1.34, я же пытался запустить с версией 6.0.3. Попробуйте изменить версию на более старую, для меня это сработало.

    Сергей, по предыдущему пункту с исключениями, в чем была ошибка? Такая же ситуация. Спасибо.

    Проблема в том, что idea убирает поля
    property name = «connection.username» > root / property >
    property name = «connection.password» > admin / property >

    Проверьте их наличие, в случае отсутствия таковых- добавьте.

    Совсем не знаю maven потому и вопрос, пакет ru.javastudy.hibernate в ручную создаются? или maven’ ом т.к. прописано в pom.xml

    И ещё вопрос как в реальных проектах принято добавлять записи в БД через один объект
    ContactEntity contactEntity = new ContactEntity ( ) ;

    contactEntity . setBirthDate ( new java . util . Date ( ) ) ;
    contactEntity . setFirstName ( «Nick» ) ;
    contactEntity . setLastName ( «VN» ) ;
    и просто переменить аргументы метода с «Nick» и «VN» на допустим свой класс string типа:

    contactEntity . setFirstName ( MyStringName ) ;
    contactEntity . setLastName ( MyString LastName ) ;

    или на каждую запись создавать новый
    ContactEntity contactEntity = new ContactEntity ( ) ;
    ?

    в pom.xml прописываются зависимости, которые потом подгрузятся как jar файлы. Пакеты никакие не создаются.

    Как писать в базу зависит от того как хранятся объекты и откуда берутся данные и собственно как происходит запись в БД. В общем плодить любые лишние объекты это расход ресурсов.

    Ясно, спасибо за быстрый ответ на счёт Maven

    Ну допустим данные берутся с формочки из полей text тогда contactEntity должен быть один? и просто его методы подставлять аргументы string и всё?

    кстати что то у меня ошибка:

    SessionFactory failed org.hibernate.MappingException: Repeated column in mapping for entity: org.learning.hibernate.dao.ContactHobbyDetailEntity column: contact_ >

    пытался исправить, что в ошибке требует, не помогло, всё сделал точно так как в туториале тут всё изменил как и написанно:-

    //NOTE that autocreate was insertable = true, updatable = true, but need both false .
    @Column(name = «hobby_ > @Id
    public String getHobbyId() <
    return hobbyId;
    >
    но ошибку то бьёт почему то в классе ContactHobbyDetailEntity не знаете почему?

    Например в JSF у вас будет один ContactEntity с set\get методами. И этот один объект будет изменяться и записываться в базу. Для других случаев может создаваться новый объект для новых данных.
    В трейсе ошибки написано что нужно сделать (повторяется колонка и параметры маппинга не те).

    параметры маппинга были заданны точ в точ как у вас в туториале 3 раза перепроверял, странно, но вот в классе ContactHobbyDetailEntity действительно сгенерилось немного не так:

    @ManyToOne
    @JoinColumn(name = «contact_ > public ContactEntity getContactByContactId() <
    return contactByContactId;
    >

    а у вас в исходниках:

    @ManyToOne
    @JoinColumn(name = «hobby_ > public HobbyEntity getHobbyByHobbyId() <
    return hobbyByHobbyId;
    >

    и название класса другое…странно… с чего бы это… ведь и базу с который генерил у вас же взял…

    сменил на @JoinColumn(name = «hobby_id, … ) и всё заработало… станно всё это…

    В любом случае спасибо Вам за ваши труды и коментарии

    Спасибо, очень полезный туториал! Все заработало, пусть не с первого раза, но заработало) =)!

    Тем, кто так и не решил ошибку компиляции даю совет — проверьте настройки hibernate.cfg.xml. Я удалила все xml с настройками для классов, оставила только Entity, то есть только классы. Обнаружила, что у меня не был прописан диалект, логин и пароль. После настройки этого, все заработало.

    Подскажите, пожалуйста, информацию по автоматическому созданию связей между таблицами.

    Шикарный туториал. Огромное человеческое спасибо!

    Огромное Вам спасибо за статью! Но у меня возникла ошибка и не понимаю как её исправить, вроде бы все xml на месте, не могли бы Вы помочь разобраться

    авг 20, 2020 4:48:50 PM org.hibernate.Version logVersion
    INFO: HHH000412: Hibernate Core <5.0.1.Final>
    авг 20, 2020 4:48:50 PM org.hibernate.cfg.Environment
    INFO: HHH000206: hibernate.properties not found
    авг 20, 2020 4:48:50 PM org.hibernate.cfg.Environment buildBytecodeProvider
    INFO: HHH000021: Bytecode provider name : javassist
    авг 20, 2020 4:48:51 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager
    INFO: HCANN000001: Hibernate Commons Annotations <5.0.0.Final>
    Exception in thread «main» java.lang.ExceptionInInitializerError: Initial SessionFactory failedorg.hibernate.boot.MappingNotFoundException: Mapping (RESOURCE) not found : ru/javastudy/hibernate/dao/ContactEntity.hbm.xml : origin(ru/javastudy/hibernate/dao/ContactEntity.hbm.xml)
    at ru.javastudy.hibernate.utils.HibernateSessionFactory.buildSessionFactory(HibernateSessionFactory.java:28)
    at ru.javastudy.hibernate.utils.HibernateSessionFactory. (HibernateSessionFactory.java:13)
    at ru.javastudy.hibernate.main.AppMain.main(AppMain.java:15)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

    В ошибке сказано не найден маппинг ru/javastudy/hibernate/dao/ContactEntity.hbm.xml. Смотрите куда потеряли (или, скорее всего, его не видит приложение). Кстати вполне гуглится куча ответов по этой ошибке…

    Здравствуйте. Такой вопрос: можно ли быстро и безболезненно прикрутить к этому проекту Spring MVC, а также заменить БД на Postgresql, или даже и пытаться не стоит?

    Можно и это не очень долго. Можете почитать другие статьи по настройке Spring MVC на этом или других сайтах.

    у меня аналогичная ошибка:

    INFO: HHH000369: Error stopping service [class org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl] : java.lang.NullPointerException
    Exception in thread «main» java.lang.ExceptionInInitializerError: Initial SessionFactory failedorg.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
    at ru.javastudy.hibernate.utils.HibernateSessionFactory.buildSessionFactory(HibernateSessionFactory.java:25)
    at ru.javastudy.hibernate.utils.HibernateSessionFactory. (HibernateSessionFactory.java:10)
    at ru.javastudy.hibernate.main.AppMain.main(AppMain.java:12)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

    подскажите, пожалуйста, как поправить

    Вставил в сгенерированный hibernate.cfg.xml вот эти строчки и все завелось:

    Подскажите, почему после запуска main, программа не завершается? В таблицу данные добавляются, но процесс выполнения не завершается.. Так надо?

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

    Цукерберг рекомендует:  Верстаем вместе с 960 Grid
Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих