1.19.2015

2. Наш первый проект. Hibernate




Нам нужно создать наш первый проект. Вы можете скопировать пример из исходников, которые можно скачать отсюда https://code.google.com/p/code-vit/issues/detail?id=2.  Практически всегда, я пользуюсь при создании нового проекта,  maven’ом ,  ввожу команду mvn archetype:generate в командной строчке и ищу подходящий мне archetype.  Потом я из него делаю шаблон,  с нужными  мне зависимостями и после создаю свой archetype командой  mvn archetype:create .   Устанавливаю   его в локальный репозиторий, на диске он будет находится в каталоге  ~/.m2/archetype-catalog.xml. Я советую вам поступать подобным образом, у вас всегда будет под рукой нужный шаблон проекта, с нужными вам библиотеками и загруженными зависимостями в локальном каталоге maven. Для простоты рассмотрим пример из исходника ch1,  сначала попробуем разобраться в наших зависимостях. Откроем pom.xml найдем в нем <dependencies>  это будут наши зависимости, которые нам нужны для работы hibernate:
<dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.1.7.Final</version>
        </dependency>

<!-- Hibernate  logging -->
       <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.1</version>
        </dependency>

        <!-- The tutorials use the H2 in-memory database -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.2.145</version>
        </dependency>                      
    </dependencies>

В первой зависимости мы подключаем hibernate-core  набор минимальных библиотек для работы с hibernate.  Во второй и третьей зависимости мы подключаем логирование для ведения  лога отладки, который поможет нам понять,  что же твориться внутри hibernate.  В четвертой зависимости мы загружаем библиотеку базы данных H2, которая полностью написана на java и которая на первом этапе поможет нам избежать ненужной настройки и установки  базы данных, такой как mysql или postgresql  на пример. Перейдите в каталог code\c2\src\main\resources , вы тут найдите файл hibernate.cfg.xml. Это основной конфигурационный файл Hibernate хранящий  настройки,  давайте перечислим самые часто используемые из них:




Свойство


Описание
connection.driver_class
Драйвер, используемый для подключения к базе данных
connection.url
Строка подключения  к базе данных
connection.username
Имя пользователя базы данных
connection.password
Пароль пользователя базы данных
dialect
Диалект базы данных, который будет использоваться hibernate, вы наверное знаете что есть международные стандарты sql  языка, но у каждой базы данных своя реализация, и соответственно некоторые стандарты могут быть  не реализованы, или реализованы частично, или специфически. Вот что бы предусмотреть  нестандартные, специфические команды sql  конкретной базы данных и ввели в hibernate  диалект. Перечислю несколько из них: H2Dialect, MySQL5Dialect, Oracle10gDialect, PostgreSQL81Dialect.  Полный список диалектов вы можете найти в пакете org.hibernate.dialect.* 
show_sql
При разработке и тестировании приложения, всегда бывает полезно, знать какие действия выполняет Hibernate.  Включает логирование действий Hibernate, очень полезная вещь, всегда советую включать при разработке приложения.
hbm2ddl.auto
Включает создание таблиц базы данных, на основе наших  классов сущностей.
hibernate.jdbc.fetch_size
Размер выборки данных из результирующего набора данных JDBC.  Допустим, hibernate  отправил sql  запрос на выборку данных и в него, попало 100 записей.  Размер выборки 20. Hibernate  потребуется 5 раз сделать выборку из результирующего набора JDBC.
hibernate.jdbc.batch_size
Указывает Hibernate сколько операций обновления нужно сгруппировать в “пакет” и отправить их всех вместе.  Допустим  у нас изменилось 20 записей. Размер группировки 5, т.е. каждые 5 записей будут группироваться в пакет, и уходить на обновление в базу данных, всего будет сделано 4 обновления.
max_fetch_depth
Устанавливает глубину выборки зависимых классов, т.е. сколько  зависимых классов будет заполнено данными.  Этот параметр может  помочь избежать ненужной выборки данных для классов, и выполнения излишних sql запросов к базе данных, так как иногда бывает, не известно нужна ли будет информация по зависимым классам, или нет.

В файле log4j.properties  находятся настройки для логирования проекта в целом и в частности Hibernate.  Давайте создадим класс Person , который будет отображать одну запись из таблицы Persons в базе данных H2. Таблица Persons будет содержать  уникальный ключ personId, имя – fName и фамилию  – sName.  Нам нужно как-то  сопоставить таблицу Persons  и поля таблицы, с классом Person  и полями класса, в этом нам помогут аннотации.  Давайте взглянем на класс Person.java :
package org.vit.ch1;

import javax.persistence.*;
import java.io.Serializable;

@Entity @Table(name = "Persons")
public class Person implements Serializable {
    static final long serialVersionUID = -7593775012501239455L;
    @Id  @GeneratedValue(strategy = GenerationType.AUTO)  @Column(name = "personId")
    private Long id;
    @Column(name = "fName")
    private String firstName;
    @Column(name = "sName")
    private String secondName;

public Person() {
    }
 
 getters and setters

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", firstName='" + firstName + '\'' +
                ", secondName='" + secondName + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person)) return false;

        Person person = (Person) o;

        if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) return false;
        if (id != null ? !id.equals(person.id) : person.id != null) return false;
        if (secondName != null ? !secondName.equals(person.secondName) : person.secondName != null)                                     return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = id != null ? id.hashCode() : 0;
        result = 31 * result + (firstName != null ? firstName.hashCode() : 0);
        result = 31 * result + (secondName != null ? secondName.hashCode() : 0);
        return result;
    }
}

Класс Person представляет собой POJO  класс,  отображение таблицы  на класс называется сущностью.  Сущность можно реализовать двумя способами. Первый способ это сначала отдельно создать таблицу Persons  и связать ее с POJO  классом Person.java .  И второй способ это создать сначала POJO класс Person  и включить  в конфигурационном файле  hibernate.cfg.xml параметр hbm2ddl.auto,  который попросит Hibernate  сгенерировать схемы таблиц базы данных на основе POJO класса. Пойдем наипростейшим путем, выберем  второй способ и поставим значение в свойстве  hbm2ddl.auto равным create,  которое будет постоянно требовать,  пересоздавать, пустую таблицу Persons при каждом запуске нашей программы.  При окончательном развертывании приложения в продуктивной среде, я бы вам советовал все ключи,  индексы,   триггеры, перечисления именовать  вручную  с  подходящими по смыслу  именами.  И к ним же добавить все схемы таблиц баз данных т.е.  прописать в  отдельном  sql файле  весь  сценарий  создания базы данных. Продолжим, и так что бы наш класс Person  правильно взаимодействовал с hibernate  и  стал сущностью,  нужно, во-первых применить к нему аннотации и второе POJO объект должен удовлетворять следующим требованиям:

1. Иметь конструктор по умолчанию без параметров.
2. Переопределять методы toString(), Equals(), HashCode().
3. Иметь поле "первичного ключа"  (id) как минимум, как максимум сложный составной ключ, это условие не обязательно,  сущность может и не иметь ключа, но это чревато большими проблемами.
4. Класс сущности не должен быть объявлен как final класс.
5. Класс сущности должен наследовать интерфейс Serializable.
6. Должны быть getters  и setters  методы как у любого POJO класса.

Немножко отступим от темы и поговорим о интерфейсе Serializable. С каждым сериализуемым классом связан уникальный идентификационный номер serialVersionUID. Если вы не указываете этот идентификатор явно, декларируя поле  private static final long с названием serialVersionUID, система генерирует его автоматически,  используя для класса сложную схему расчетов. При этом на автоматически генерируемое значение оказывают влияние название класса, названия реализуемых им интерфейсов, а также все открытые и защищенные члены. Если вы каким-то образом поменяете что-либо в этом наборе, например, добавите новый метод, изменится и автоматически генерируемый serial versionUID.  Следовательно, если вы не будете явным образом декларировать этот идентификатор, совместимость с предыдущими версиями будет потеряна. Клиенты, которые пытаются сериализовать объект с помощью старой версии класса и десериализовать его уже с помощью новой версии, получат сбой программы. И так, как создать static final long serialVersionUID?  Пусть у нас есть скомпилированный класс src/org/vit/Person.class
перейдем в папку src и запустим команду serialver которая входит в jdk:

C:\...\src>serialver org.vit.ch1.Person
org.vit.ch1.Person:
static final long serialVersionUID = -7593775012501239455L;
C:\...\src>

копируем и вставляем в наш класс:

public class Person implements Serializable{
    static final long serialVersionUID = -7593775012501239455L;
...
}

Осталось только поговорить об аннотациях.  То, что класс является сущностью,  и с ней будет работать Hibernate, говорит нам аннотация @Entity. На какую таблицу базы данных будет отображаться  наш  POJO  класс, говорит нам аннотация  @Table(name = "Persons"), параметр name   задает нам имя таблицы. Если имя таблицы базы данных не задано в атрибуте name,  то имя класса и будет именем таблицы.  Любая таблица баз данных должна иметь уникальный первичный ключ   для того,  что бы по ключу можно было найти запись и идентифицировать её. Hibernate будет использовать этот ключ как уникальный идентификатор, при работе с объектами сессии.  Ключ объявляется аннотацией  @Id.  Любой ключ должен быть сгенерирован по каким-нибудь правилам, аннотация @GeneratedValue(strategy = GenerationType.AUTO)  отвечает за генерацию ключа .  Атрибут strategy отвечает за метод генерации ключа.  Допустим  SEQUENCE использует последовательность  (sequence) в PostgreSQL, Oracle,  у этих баз данных автоинкремент поля,  реализован через  перечисление.  IDENTITY реализует,  identity колонки в   MySQL, MS SQL Server.  Для нашего примера установим атрибут  strategy в значение  GenerationType.AUTO  т.е hibernate  сам решит какую стратегию генерации ключа лучше выбрать для нашего диалекта базы данных H2 (org.hibernate.dialect.H2Dialect).  Осталось рассказать о последней аннотации @Column(name = "personId").  Эта аннотация соотносит,  другими словами делает маппинг  полей класса на поля таблицы базы данных, для нашего примера поле Id класса Person  будет соотнесено полю personId таблицы Persons.  И последние,   для hibernate нужно указать в файле настроек hibernate.cfg.xml   строчку, с каким  классом hibernate должен работать как с сущностью:

<!-- Names the annotated entity class -->
 <mapping class="org.vit.ch1.Person"/>

Осталось нам взглянуть, как работает hibernate,  и реализовать 2 операции это вставка записи в базу данных, и её чтение.  Основной рабочей лошадкой в Hibernate  является объект Session. Чтобы получить session,  нужно создать фабрику классов  SessionFactory.  Для создания фабрики классов нужен файл  hibernate.cfg.xml, откуда будут считаны все конфигурационные настройки.  Я всегда стараюсь использовать его,   по умолчанию hibernate сначала ищет файл настроек hibernate.properties, который в большем приоритете, чем файл  hibernate.cfg.xml, это нужно иметь ввиду если вы пользуетесь двумя файлами настройки.  В вашей программе может быть создано несколько объектов SessionFactory  с различными конфигурациями,  на пример, можете настроить  2 конфигурационных файла hibernate.cfg.xml  для работы с различными базами данных одновременно.  У объекта SessionFactory мы должны запросить объект session, который  будет создан или возвращен нам из пула объектов  SessionFactory.  Зачем нам нужен объект SessionFactory? Он управляет жизненным циклом объектов session.  Как получили сессию, всегда работа с сессией начинается  с запроса новой транзакции (.beginTransaction). Транзакция  должна быть подтверждена(.commit) или отменена(.rollback). При отмене транзакции все данные откатываются до состояния начала транзакции.  После того как мы завершили работу  с  объектом  session мы должны его закрыть  (.close), вернуть ресурсы  объекту  SessionFactory.  Внутри  транзакции мы можем применить к  объекту класса Person  операции:   создать  данные в таблице (.save),  считать данные в объект Person  (.get) по ключу id, удалить  данные (.delete).  И так взглянем на пример:

  public class First {
    private SessionFactory sessionFactory;

    public First() {
        try {
            sessionFactory = new Configuration().configure().buildSessionFactory();
            Session session = null;
            try {
            session = sessionFactory.openSession();
            session.getTransaction().begin();
            Person person = new Person();
            person.setFirstName("Vitaly");
            person.setSecondName("Lopanov");
            session.save(person);
            System.out.printf("Insert Person: %s\n", person);
            Long id = person.getId();
            session.getTransaction().commit();
            session.close();
            session = sessionFactory.openSession();
            session.getTransaction().begin();
            Person person2 = (Person) session.get(Person.class, id);
            System.out.printf("Person select: %s\n", person2);
            session.getTransaction().commit();
            } finally {
              if (session!=null) session.close();
            }

        } finally {
            if (sessionFactory != null) sessionFactory.close();
        }
    }

    public static void main(String args[]) {
        new First();
    }
}

Из лога видно, что  Hibernate создает таблицу, генерирует sql  команды insert и select,  при этом мы только создаем объект Person  и  указываем session  что делать с объектом Person, все просто на первый взгляд:

2014-08-06 21:21:40,399 DEBUG [org.hibernate.SQL] - <drop table Persons if exists>
Hibernate: drop table Persons if exists  2014-08-06 21:21:40,400 DEBUG [org.hibernate.SQL] - <create table Persons (personId bigint generated by default as identity, fName varchar(255), sName varchar(255), primary key (personId))>
Hibernate: create table Persons (personId bigint generated by default as identity, fName varchar(255), sName varchar(255), primary key (personId))
2014-08-06 21:21:40,470 DEBUG [org.hibernate.SQL] - <insert into Persons (personId, fName, sName) values (null, ?, ?)>
Hibernate: insert into Persons (personId, fName, sName) values (null, ?, ?)
Insert Person: Person{id=1, firstName='Vitaly', secondName='Lopanov'}
2014-08-06 21:21:40,507 DEBUG [org.hibernate.SQL] - <select person0_.personId as personId0_0_, person0_.fName as fName0_0_, person0_.sName as sName0_0_ from Persons person0_ where person0_.personId=?>
Hibernate: select person0_.personId as personId0_0_, person0_.fName as fName0_0_, person0_.sName as sName0_0_ from Persons person0_ where person0_.personId=?
Person select: Person{id=1, firstName='Vitaly', secondName='Lopanov'}

Комментариев нет:

Отправить комментарий