Когда дело доходит до постоянства объектов, тандем JPA / Hibernate является одним из тех драгоценных камней, которые вы никогда не захотите оставить, когда привыкнете к нему. Торбен Янссен написал отличное введение в предмет, перечисляя пять веских причин, почему его использовать. (Это не является обязательным условием для этой статьи, но я, тем не менее, настоятельно рекомендую вам прочитать ее.) В этом руководстве мы сосредоточимся на основной функции Hibernate: сохранении графов объектов.
Зачем нам нужна автоматическая настойчивость? — чехол для Hibernate
Давайте сначала ответим на вопрос, почему мы бы даже использовали каркас для сохранения данных приложения. JDBC (Java Database Connectivity) недостаточно хорош для нас?
Что ж, если мы посмотрим на данные, хранящиеся в наших приложениях, мы обычно увидим сложную сеть объектов, также известную как граф объектов, которую мы хотим записывать и читать из какого-то хранилища, обычно это реляционная база данных. К сожалению, процесс настойчивости далеко не прост. Преобразование целых графов объектов в простые табличные данные — это совсем не просто, а хранение объектов с использованием простого JDBC является болезненным и громоздким, так как стандарт предоставляет низкоуровневый API. Знаете, подключение к базе данных и написание SQL-запросов с нуля — это хорошо. Но будьте честны с самим собой: вы хотите делать это снова и снова? Если вы чем-то похожи на меня, то нет.
И вот тут-то и приходит Hibernate . Помимо множества других вещей, он позволяет нам создавать объекты, которые содержат наши данные, называемые сущностями, к которым мы привыкли, и автоматизировать процесс их чтения и записи.
Давайте начнем!
Разработка базового Java-приложения — Начало работы с Hibernate
Хороший способ начать тянуть бразды правления Hibernate — создать простой пример. Для этого я собираюсь разработать наивное Java-приложение, функциональность которого сводится к выполнению операций CRUD над некоторыми объектами пользователя. В свою очередь, объекты будут сохранены, извлечены, обновлены и удалены из базы данных MySQL.
Чтобы все шло как положено, сначала нужно получить требуемые JAR-файлы Hibernate ORM вместе с соответствующим MySQL Java Connector. Maven сделает это хорошо для нас, поэтому мы можем сосредоточиться на кодировании, а не на решении проблем зависимости.
Вот мой файл POM:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.electricalweb.hibernatepplication</groupId> <artifactId>hibernateapplication</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.2.Final</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>6.0.3</version> </dependency> </dependencies> </project>
В этом конкретном случае я буду использовать IntelliJ IDEA в качестве своей личной IDE, но проект можно легко создать в NetBeans , Eclipse или IDE по вашему выбору.
Теперь, когда готовится файл POM, вот как будет выложен этот пример приложения:
Как показано выше, каркас приложения структурирован только в трех пакетах: com.electricalweb.entities
, который содержит класс домена, который моделирует пользователей, com.electricalweb.daos
, в котором находится базовый класс DAO пользователя, и, наконец, com.electricalweb.application
, которая является точкой входа приложения. Поскольку основной целью является сохранение пользовательских объектов в MySQL, давайте определим класс домена User
.
Определение персистентного объекта — основная особенность Hibernate
Для простоты, класс домена будет чрезвычайно анемичным, имея только несколько личных свойств, name
и email
, наряду с параметризованным конструктором и типичными установщиками / получателями. Вот этот класс:
@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String name; private String email; public User() {} public User(String name, String email) { this.setName(name); this.setEmail(email); } // getters and setters for name and email }
Да, ты прав. Приведенный выше класс представляет собой простой контейнер данных, к которому не привязано поведение (не стесняйтесь судить меня за совершение этого кардинального греха) Единственная деталь, на которую стоит обратить внимание, это то, что для того, чтобы сделать ее устойчивой для Hibernate (или любой другой реализации JPA), она была помечена аннотацией @Entity
. Кроме того, Hibernate должен знать, как обрабатывать первичные ключи сущностей.
Чтобы справиться с этим, @Id
и @GeneratedValue(strategy = GenerationType.AUTO)
инструктируют Hibernate автоматически генерировать ID для каждого объекта пользователя, который будет сопоставлен первичному ключу соответствующей записи базы данных. Наконец, @Table(name = "users")
указывает Hibernate отображать экземпляры класса в строки в таблице users
.
Когда класс домена уже определен, последний шаг, который необходимо предпринять для запуска и запуска примера приложения, — это создать файл конфигурации, который не удивительно называется persistence.xml
, который будет содержать все данные, необходимые для подключения к базе данных и сопоставления сущностей. в базу данных.
Доступ к базе данных — создание модуля сохранения
Несомненно, наиболее распространенным способом настройки параметров подключения к базе данных, наряду с дополнительными проприетарными опциями Hibernate, является определение вышеупомянутого файла persistence.xml
. Процесс довольно скучный, но чрезвычайно простой, поверьте мне.
Вот как может выглядеть типичная версия этого файла:
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0"> <persistence-unit name="user-unit" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <class>com.electricalweb.entities.User</class> <properties> <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/usersdb" /> <property name="javax.persistence.jdbc.user" value="username" /> <property name="javax.persistence.jdbc.password" value="password" /> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" /> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> <property name="hibernate.hbm2ddl.auto" value="update"/> </properties> </persistence-unit> </persistence>
Как видите, файл легко читается сверху вниз. Он просто устанавливает несколько параметров подключения к базе данных, с которыми вам следует ознакомиться, если вы уже работали с JDBC, включая URL базы данных, драйвер JDBC, имя пользователя базы данных и пароль (убедитесь, что вы указали правильные значения для вашего собственный сервер базы данных).
Кроме того, и это, безусловно, самый интересный сегмент файла, он определяет единицу постоянства , которая представляет собой набор сущностей персистентности (в данном случае это просто пользователи, но это могут быть несколько сущностей, заключенных в несколько тегов <class>
) которые обрабатываются менеджером сущностей JPA. Учитывая, что в этом случае Hibernate является выбранной реализацией JPA, объектами будет управлять менеджер объектов Hibernate.
Если вы похожи на меня, то в этот момент вам будет интересно посмотреть, как использовать менеджер сущностей для выполнения операций CRUD на нескольких пользовательских сущностях в одно мгновение. Просто расслабься. Следующий раздел подробно описывает процесс.
Выполнение операций CRUD над объектами — Работа с Entity Manager в Hibernate
Короче говоря, запуск CRUD-операций на пользовательских объектах намного проще, чем можно подумать. Весь процесс сводится к порождению EntityManager
и использованию его API. Это действительно так просто.
Например, мы могли бы использовать точку входа приложения, которая является простым классом с простым статическим main
методом, и вызвать диспетчер сущностей следующим образом:
EntityManager entityManager = Persistence .createEntityManagerFactory("user-unit") .createEntityManager(); EntityTransaction entityTransaction = entityManager.getTransaction(); /* Persist a User entity */ entityTransaction.begin(); User user = new User("Alejandro", "[email protected]"); entityManager.persist(user); entityTransaction.commit(); /* Fetch a User entity */ entityManager.find(User.class, 1); /* Update a User entity */ entityTransaction.begin(); User user = entityManager.find(User.class, 1); user.setName("Alex"); user.setEmail("[email protected]"); entityManager.merge(user); entityTransaction.commit(); /* Remove a User entity */ entityTransaction.begin(); User user = entityManager.find(User.class, 1); entityManager.remove(user); entityTransaction.commit(); entityManager.close();
Довольно просто, не правда ли? Сохранение, выборка, обновление и удаление пользовательских объектов с помощью диспетчера неуправляемых объектов — это простой процесс, который можно освоить буквально за считанные минуты. Процедура ограничивается сначала получением менеджера через EntityManagerFactory
, затем захватом экземпляра класса EntityTransaction
и, наконец, вызовом нужного метода менеджера сущностей.
В приведенном выше фрагменте кода предполагается, что существует таблица базы данных MySQL с именем уже определенных users
, а также экземпляр MySQL, работающий на локальном сервере. Поэтому убедитесь, что настроили это перед тестированием примера приложения.
К этому моменту вы, надеюсь, изучили основы сохранения отдельных объектов в Hibernate. Итак, что будет дальше? Ну очень много на самом деле. Как вы, возможно, заметили, я оставил за кадром персистентные сущности, которые имеют определенные отношения с другими сущностями (да, аспект отношений). Я сделал это сознательно, чтобы весь процесс обучения был свободен от дополнительных, ненужных сложностей. В конце концов, это всего лишь введение в использование Hibernate.
Тем не менее, я бы хотел немного отточить демонстрацию, так как в ее текущем состоянии она выглядит довольно монолитно, и поток ее выполнения не может контролироваться через пользовательский интерфейс.
Принимая это во внимание, в дальнейшем я буду определять класс DAO , который будет инкапсулировать обработку менеджера сущностей и транзакции сущностей за пределами декларативного API. Наконец, вы увидите, как использовать класс для выборочного выполнения методов CRUD для пользовательских объектов на основе параметров, введенных в консоли Java.
Добавление Nice API — Определение класса DAO
Поверь мне. Я не слепой поклонник шаблонов дизайна, использую их повсюду без веских причин для этого. В этом случае, однако, стоит обратиться к функциональности, предоставляемой шаблоном DAO , поэтому вы можете попробовать предыдущий пример без особых хлопот.
UserDao
говоря, все, что UserDao
следующий класс UserDao
, — это определение легко потребляемого API, который позволяет выполнять методы CRUD для данной пользовательской сущности. Вот как выглядит класс:
public class UserDao { private EntityManager entityManager; private EntityTransaction entityTransaction; public UserDao(EntityManager entityManager) { this.entityManager = entityManager; this.entityTransaction = this.entityManager.getTransaction(); } public void persist(String name, String email) { beginTransaction(); User user = new User(name, email); entityManager.persist(user); commitTransaction(); } public User find(int id) { return entityManager.find(User.class, id); } public void update(int id, String name, String email) { beginTransaction(); User user = entityManager.find(User.class, id); user.setName(name); user.setEmail(email); entityManager.merge(user); commitTransaction(); } public void remove(int id) { beginTransaction(); User user = entityManager.find(User.class, id); entityManager.remove(user); commitTransaction(); } private void beginTransaction() { try { entityTransaction.begin(); } catch (IllegalStateException e) { rollbackTransaction(); } } private void commitTransaction() { try { entityTransaction.commit(); entityManager.close(); } catch (IllegalStateException e) { rollbackTransaction(); } } private void rollbackTransaction() { try { entityTransaction.rollback(); } catch (IllegalStateException e) { e.printStackTrace(); } } }
Честно говоря, этот дополнительный уровень абстракции (и сложности) не имеет большого смысла, если бы я не показал вам, как использовать класс в конкретном случае. Итак, проверьте следующий фрагмент кода, который использует преимущества внедрения зависимостей .
public class HibernateApplication { private final UserDao userDao; private final BufferedReader userInputReader; public static void main(String[] args) throws Exception { EntityManager entityManager = Persistence .createEntityManagerFactory("user-unit") .createEntityManager(); UserDao userDao = new UserDao(entityManager); BufferedReader userInputReader = new BufferedReader(new InputStreamReader(System.in)); new HibernateApplication(userDao, userInputReader).run(); } public HibernateApplication(UserDao userDao, BufferedReader userInputReader) { this.userDao = userDao; this.userInputReader = userInputReader; } private void run() throws IOException { System.out.println("Enter an option: " + "1) Insert a new user. " + "2) Find a user. " + "3) Edit a user. " + "4) Delete a user."); int option = Integer.parseInt(userInputReader.readLine()); switch (option) { case 1: persistNewUser(); break; case 2: fetchExistingUser(); break; case 3: updateExistingUser(); break; case 4: removeExistingUser(); break; } } private void persistNewUser() throws IOException { String name = requestStringInput("the name of the user"); String email = requestStringInput("the email of the user"); userDao.persist(name, email); } private void fetchExistingUser() throws IOException { int id = requestIntegerInput("the user ID"); User user = userDao.find(id); System.out.print("Name : " + user.getName() + " Email : " + user.getEmail()); } private void updateExistingUser() throws IOException { int id = requestIntegerInput("the user ID"); String name = requestStringInput("the name of the user"); String email = requestStringInput("the email of the user"); userDao.update(id, name, email); } private void removeExistingUser() throws IOException { int id = requestIntegerInput("the user ID"); userDao.remove(id); } private String requestStringInput(String request) throws IOException { System.out.printf("Enter %s: ", request); return userInputReader.readLine(); } private int requestIntegerInput(String request) throws IOException { System.out.printf("Enter %s: ", request); return Integer.parseInt(userInputReader.readLine()); } }
Как показано выше, класс HibernateAplication
принимает диапазон из четырех различных значений, введенных в консоли. Согласно данному вводу, он использует DAO для выполнения определенной операции CRUD на пользовательском объекте. Само собой разумеется, логика этого обновленного примера не требует пояснений, поэтому любой дальнейший анализ будет излишним. Таким образом, не забудьте попробовать его на собственной платформе разработки и не стесняйтесь крутить его по своему желанию.
Последние мысли
В этом руководстве я провел краткое руководство по использованию Hibernate для сохранения сущностей в базе данных. Естественно, платформа — это мощный зверь, который предлагает гораздо больше, чем просто функциональность ORM, включая возможности полнотекстового поиска, геолокации , проверки доменных моделей и многое другое. По понятным причинам охват всех этих функций в одной статье будет не только невозможным, но и нецелесообразным. Конечно, если вы хотите протестировать образец приложения и переработать его по своему желанию, не стесняйтесь клонировать репозиторий из GitLab (https://gitlab.com/alejandrogervasio/hibernate.git)
В любом случае, если вы новичок в Hibernate и хотите сразу же начать в него вонзать зубы, то это руководство, надеюсь, станет хорошей отправной точкой. Не стесняйтесь оставлять мне свои комментарии. Я постараюсь ответить своевременно. Увидимся в следующей статье!