Введение в Hibernate и основы ORM

Реализация object-relational mapping (O/R mapping) является общей потребностью для множества проектов по разработке программного обеспечения. Обычно работа над автоматизацией процесса хранения данных очень скучна, и при ручной реализации существует опасность возникновения ошибок. Если к этому прибавить постоянно меняющиеся требования, разработчику необходимо учитывать сложный процесс синхронизации исходного кода и структуры хранения данных. А плюс к этому необходима переносимость приложений между платформами, и все становится еще более сложным и запутанным.

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

Подготовка рабочей среды

Прежде чем начать работу вам понадобится дистрибутив Hibernate, который вы можете скачать с сайта этого продукта, сайт Мы будем использовать в примерах версии начиная со второй (2.x). В качестве базы данных остановимся на MySQL версии 4.0.16 ( сайт ). Помимо MySQL Hibernate также поддерживает множество open-source и коммерческих баз данных, например, Hypersonic SQL, PostgreSQL, Oracle, DB2 и другие.

После того как вы скачаете все необходимые пакеты, необходимо настроить нашу среду для выполнения примеров. Вообще-то все, что нам нужно сделать, это включить все загруженные из Internet JAR-файлы в вашу переменную окружения CLASSPATH. Это должно быть как минимум два файла: hibernate2.jar из директории, в которую вы распаковали дистрибутив Hibernate, и mm.mysql-2.0.13-bin.jar — JDBC-драйвер к серверу баз данных MySQL (этот драйвер также можно загрузить по ссылке с сайта сайт ). Кроме этого, Hibernate требует еще несколько дополнительных библиотек, которые находятся в директории <hibernate-dir>/lib/, где <hibernate-dir> — это директория с распакованным установочным архивом Hibernate. Из этой директории нам нужны не все JAR-файлы, но все же лучше использовать их все. Прежде чем мы приступим к исследованию Hibernate, сначала мы сформулируем проблему и попробуем ее решить средствами Hibernate.

Определяем задачу

Наверняка каждый разработчик так или иначе сталкивался с проблемой создания системы обслуживания клиентов. Общая схема может выглядеть следующим образом: создаем объект заказа (Order), заносим в него объекты продуктов (Product), которые к тому времени становятся элементами заказа (OrderItems) и после этого сохраняем заказ (Order).

Для представления базы данных, которая будет служить нашему примеру, воспользуемся следующим SQL-скриптом (для демонстрации возможностей Hibernate мы оставляем для рассмотрения только класс Product, поэтому используем лишь одну таблицу products в нашей базе данный):

DROP DATABASE HIBERNATE;
CREATE DATABASE HIBERNATE;
USE HIBERNATE;

CREATE TABLE PRODUCTS(
ID VARCHAR(32) NOT NULL PRIMARY KEY,
NAME VARCHAR(32) NOT NULL,
PRICE DOUBLE NOT NULL,
AMOUNT INTEGER NOT NULL);

Как видно, эта модель данных очень проста. В случае модели данных для реальных проектов, нам нужно будет в первую очередь добавить таблицы других классов, а также определить FOREIGN KEY поля, индексы, дополнительные поля и прочее. Однако для нашего примера такая модель вполне подходит.

Теперь приведем Java-код, который реализует описанное выше поведение, т.е. сам класс Product. Для краткости не будет приводить полные списки getter/setter методов, поскольку они будут занимать слишком много места. Вы без труда сможете их дописать сами.

Класс Product

Это самый простой из этих трех классов класс, который определяет только необходимые поля и getter/setter-методы для этих полей и конструктор по умолчанию, чтобы этот класс соответствовал спецификации JavaBeans.

public class Product {
private String id;
private String name;
private double price;
private int amount;

public String getId() {
return id;
}
public void setId(String s) {
id = s;
}
// конструктор по умолчанию и другие
// getter/setter-методы
// ...
}

Вот и все, что нужно для того, чтобы Hibernate смог работать с этим классом. Как же так? — скажете вы, — ведь он не реализует никаких внешних интерфейсов и не расширяет никаких классов! Ответ прост: Hibernate работает с любым видом Java-объектов, если они поддерживают соглашение JavaBeans (см. выше).

Решение задачи сохранения объектов в базе данных средствами Hibernate

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

Создание и сохранение продукта (Product)

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

1. Создать объект класса Product.
2. Получить net.sf.hibernate.SessionFactory с использованием net.sf.hibernate.cfg.Configuration в самом начале приложения.
3. Открыть сессию net.sf.hibernate.Session вызвав метод SessionFactory.openSession().
4. Сохранить объект класса Product и закрыть сессию (объект класса Session, созданный на 3-ем шаге).

Однако прежде чем приступать к выполнению этих шагов, вы должны определить несколько конфигурационных файлов, с помощью которых Hibernate будет знать, где нужно сохранять ваши объекты и каким образом ваши объекты будут отображаться в выбранное хранилище (таблицы базы данных).
Первый конфигурационный файл — это файл hibernate.properties. Этот файл определяет, какую базу данных мы хотим использовать, имя пользователя и пароль, и множество других опций. В нашем случае, база данных MySQL, файл hibernate.properties будет содержать следующие строки:

hibernate.connection.username=alex
hibernate.connection.password=
hibernate.connection.url=jdbc:mysql://localhost/hibernate
hibernate.connection.driver_class=org.gjt.mm.mysql.Driver
hibernate.dialect=net.sf.hibernate.dialect.MySQLDialect

Вы можете изменять эти значения в соответствии с вашими требованиями.

Следующий файл, который нам необходим — это Product.hbm.xml. Это XML-файл, который определяет, каким образом Java-объекты сохраняются (отображаются) в базе данных. В этом файле мы определяем, в какой таблице нашей базы данных данные будут записаны, какое поле в какой столбец таблицы будет отображено, как различные объекты относятся друг к другу и прочее. Вот код нашего файла Product.hbm.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class name="Product"
table="products">
<id name="id" type="string"
unsaved-value="null">
<column name="id" sql-type="char(32)"
not-null="true"/>
<generator class="uuid.hex"/>
</id>
<property name="name">
<column name="name" sql-type="char(255)"
not-null="true"/>
</property>
<property name="price">
<column name="price" sql-type="double"
not-null="true"/>
</property>
<property name="amount">
<column name="amount" sql-type="integer"
not-null="true"/>
</property>
</class>
</hibernate-mapping>

Здесь все просто и понятно. Рассмотрим некоторые детали этого XML-файла:

-- Строка <class name="Product" table="products"> говорит о том, что мы собираемся отображать класс с именем Product в таблицу products. -- Элемент <id> и его дочерние элементы задают связь между нашим Java-классом и базой данных.
-- Элементы <property> определяют, в какие столбцы каждое из полей будет сохранено, а также его тип, имя и прочее.
Элемент <generator class="uuid.hex"/> по началу не совсем понятен. Зная, что это один из дочерних элементов элемента <id>, его назначение становится более-менее понятным: поскольку наше приложение не знает, каким образом данные будут сохранены в базе данных, нам нужен суррогатный ключ. Этот ключ не будет иметь какого-либо значения в бизнес-логике приложения. Он лишь помогает Hibernate манипулировать объектами. Вновь созданный объект класса Product не имеет своего собственного определенного id, за нас его создаст Hibernate. В нашем случае мы выбрали UUID- строки, однако существует много различных предопределенных ID-генераторов. Кроме того, вы также можете написать собственный. За более детальной информацией обращайтесь к документации, поставляемой вместе с Hibernate.

Файл Product.hbm.xml должен располагаться в одном пакете с классом Product.

Теперь с этими двумя файлами мы можем реализовать алгоритм сохранения класса Product в базе данных с помощью следующего кода:

import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.cfg.Configuration;

// Используется, как:
// java InsertProduct <название> <количество> <цена>
public class InsertProduct {

public static void main(String[] args) throws Exception {

// 1. Создаем объект класса Product
Product p = new Product();
p.setName(args[0]);
p.setAmount(Integer.parseInt(args[1]));
p.setPrice(Double.parseDouble(args[2]));

// 2. Настраиваем Hibernate
Configuration cfg = new Configuration().addClass(Product.class);
SessionFactory sf = cfg.buildSessionFactory();

// 3. Открываем Session
Session sess = sf.openSession();

// 4. Сохраняем Product и закрываем Session
Transaction t = sess.beginTransaction();
sess.save(p);
t.commit();
sess.close();
}
}

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

java InsertProduct Хлеб 100 600

Теперь с помощью утилиты mysql.exe из дистрибутива MySQL вы можете посмотреть на содержимое таблицы products. Для этого запустите это приложение и введите следующие команды по очереди:
use hibernate;
select * from products;

Далее вы должны будете увидеть таблицу, которая содержит одну запись:

ID |NAME |PRICE |AMOUNT |
3f138041f947f4320ff90764f8340f01|Хлеб |600 |100 |

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

Находим и загружаем продукты (объекты класса Product)

Поиск и загрузка уже сохраненных объектов — задача очень простая для Hibernate. С использованием его языка запросов мы можем очень легко извлечь объект (или множество объектов) по его ID, имени или другим свойствам. Мы также можем извлекать либо целый объект, либо же определенные его свойства по отдельности. Давайте рассмотрим класс FindProductByName:

import java.util.List;
import net.sf.hibernate.Hibernate;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.cfg.Configuration;
import Product;

// Используется, как
// java FindProductByName <название>
public class FindProductByName {

public static void main(String[] args) throws Exception {
// запрос
String query =
"select product from product in class test.hibernate.Product where product.name=:name";

// что ищем?
String name = args[0];

// инициализация
Configuration cfg = new Configuration().addClass(Product.class);

SessionFactory sf = cfg.buildSessionFactory();

// открываем Session
Session sess = sf.openSession();

// поиск, результат в list
List list = sess.find(query, name, Hibernate.STRING);

if (list.size() == 0) {
System.out.println("Нет продуктов с именем " + name);
System.exit(0);
}
Product p = (Product) list.get(0);
sess.close();
System.out.println("Найден продукт: " + p);
}
}

Рассмотрим самые интересные места приведенного выше кода:
-- В коде встречается запрос (query) с where-выражением. Все очень схоже с обычным SQL-форматом.
-- Мы инициализируем Hibernate также как и в первом примере (сохранение объекта). На этот раз у нас уже есть созданные файлы конфигурации и mapping'а (XML-файл).
-- Метод sess.find() выполняет запрос и устанавливает предоставленное ему имя продукта, как аргумент для поиска типа Hibernate.STRING. -- В результате мы имеем объект класса java.util.List, заполненный объектами Product из базы данных, которые удовлетворяют условию поиска. -- Выражением Product p = (Product) list.get(0); мы извлекаем первый найденный объект.

Обновление и удаление продуктов (Product)

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

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

double percentage = Double.parseDouble(args[0])/100;

sess = sf.openSession();
Transaction t = sess.beginTransaction();

// переменная list содержит список объектов класса Product
Iterator iter = list.iterator();
while (iter.hasNext()) {
Product p = (Product) iter.next();
p.setPrice(p.getPrice() * (1 + percentage));
sess.saveOrUpdate(p);
}
t.commit();
sess.close();

И, наконец, для удаления продукта, мы должны вызывать метод sess.delete(product) . Не забывайте также выполнять commit() в конце транзакции, чтобы подтвердить изменения, если только конечно опция autocommit вашей базы данных не включена.

Таким образом, мы рассмотрели все базовые операции — создание, чтение, обновление и удаление. На этом первое ознакомление с Hibernate можно завершить. Чтобы узнать больше об этом продукте обратитесь к документации, которую разработчики любезно предоставляют совершенно бесплатно.

Резюме

Эта статья показывает, насколько мощным инструментом является Hibernate. Из нее вы узнали, как очень просто можно сохранять любого вида Java- объекты, а после ими манипулировать. Но возможности Hibernate несравнимо шире, чем простой набор этих базовых операций. Hibernate может полноценно управлять транзакциями с поддержкой операций commit, rollback, наследование, некоторые типы коллекций. Hibernate также вооружает разработчика очень мощным объектно-ориентированным языком запросов, HQL, который имеет множество различных полезных возможностей, с которыми вы можете ознакомиться, прочитав. Удачи вам в освоении Hibernate!

Алексей Литвинюк (c), litvinuke@tut.by


Компьютерная газета. Статья была опубликована в номере 32 за 2005 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета