Использование Sequence в Hibernate

Согласно спецификации JPA возможно 4 различных варианта программного генерирования первичного ключа или использование возможностей сервера БД (колонка auto-incremented в таблице или генератор последовательностей Sequence). Единственное, что необходимо выполнить при описании сущности, так это добавить аннотацию @GeneratedValue к первичному ключу и описать стратегию :

@Id
@GeneratedValue(strategy = GenerationType.ХХХХ)
@Column(name = "id", updatable = false, nullable = false)
private Long id;

В данной статье будет рассмотрен вопрос генерирования целочисленного идентификатора (первичного ключа) с помощью Sequence в Hibernate. В качестве подопытной базы данных возьмем Oracle и подготовим всё необходимое для эксперимента. Начнем с базы данных : создадим таблицу USERS и генератор последовательностей SEQ_USER.

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

create table USERS (
   id        Integer not null,
   login     varchar2 (16) null,
   name      varchar2 (64) null,
   data      timestamp default SYSDATE,
   CONSTRAINT PK_USERID PRIMARY KEY (id)
);

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

Как создавать и работать с генераторами последовательностей Sequence подробно представлено здесь.

create sequence SEQ_USER
minvalue 1
start with 5
increment by 1
cache 5;

Проект тестирования Sequence и Hibernate

После того, как объекты БД созданы, создадим проект hibernate-seq, в котором будет генерировать первичный ключ с использованием последовательности SEQ_USER и вносить записи в таблицу USERS. На следующем скриншоте представлена структура проекта в среде разработки Eclipse. В проекте необходимо определить файл конфигурации hibernate.cfg.xml и сущность/класс User. В модуле HibernateExample будем тестировать наши настройки. Все необходимые для работы с Oracle и hibernate библиотеки размещены в поддиректории lib.

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

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

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

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
  <session-factory>
    <!-- Database connection settings -->
    <property name="hibernate.connection.driver_class">
            oracle.jdbc.OracleDriver</property>
    <property name="hibernate.connection.url">
            jdbc:oracle:thin:@localhost:1521:SE</property>
    <property name="hibernate.connection.username">
            scott</property>
    <property name="hibernate.connection.password">
            tiger</property>

    <!-- JDBC connection pool (use the built-in) -->
    <property name="hibernate.connection.pool_size">1</property>
    <!-- Echo all executed SQL to stdout -->
    <property name="show_sql">true</property>
    <!-- SQL dialect -->
    <property name="hibernate.dialect">
            org.hibernate.dialect.Oracle10gDialect</property>
    <property name="hibernate.current_session_context_class">
            thread</property>
    <property name="hibernate.connection.CharSet">
            utf8</property>
    <property name="hibernate.connection.characterEncoding">
            utf8</property>
    <property name="hibernate.connection.useUnicode">
            true</property>
    <!-- Сущность/класс User -->
    <mapping class="net.common.model.User"/>

  </session-factory>
</hibernate-configuration>

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

При описании сущности/класса User используем аннотации. Подробная информация об аннотациях JPA представлена здесь.

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;

import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.SequenceGenerator;

@Entity
@Table(name = "USERS")
public class User 
{
    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE,
                    generator="users_seq")
    @SequenceGenerator(name="users_seq",
                       sequenceName="SEQ_USER", allocationSize=10)
    @Column(name="id", updatable=false, nullable=false)
    private Integer  id;

    @Column (name="name", nullable=true)
    private String name;

    @Column (name="login")
    private String  login;

    public User() {}

    public User(Integer id, String login, String name)
    {
        super();
        this.id    = id;
        this.login = login;
        this.name  = name;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }

    public String getLogin() {
        return login;
    }
    public void setLogin(String login) {
        this.login = login;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Описание аннотаций сущности

@Table
В связи с тем, что наименование класса User отличается от наименования таблицы в БД, то необходимо перед описанием класса определить аннотацию @Table с указанием наименования таблицы БД USERS.

@Id
Одно из требований фреймворка связано с идентификатором Id — каждая таблица должна иметь такой идентификатор. Анотация @Id определяет поле сущности, которое будет использовано в качестве такого идентификатора.

@Column
Если наименование поля @Id таблицы и переменной сущности не совпадают, то в аннотации @Column следует указать наименование поля таблицы. Несмотря на то, что в нашем случае поля совпадают, «правилом хорошего тона» считается определение данной аннотации в коде. Для программиста это не трудоемко, а вот если другому разработчику придется столкнуться с этим кодом, то не придется лишний раз лезть в БД. Дополнительные атрибуты аннотации updatable и nullable несут информативную составляющую.

@GeneratedValue
Аннотация @GeneratedValue атрибутом strategy определяет стратегию генерации уникального идентификатора с использованием SEQUENCE. Атрибут generator связывает данную аннотацию с аннотацией @SequenceGenerator.

@SequenceGenerator
В аннотации @SequenceGenerator атрибутом sequenceName определяется генератор последовательности. Атрибутом name данная аннотация связывается с @GeneratedValue. Особый интерес связан с атрибутом allocationSize. Обратите внимание, что последовательность SEQ_USER начинает отсчет со значения 5 (start with 5), а значение allocationSize равно 10. В результате 1-ое значение идентификатора будет 50 (это мы увидим ниже). Если бы значение allocationSize равнялось 5, то значение 1-го идентифкатора равнялось 25. По умолчанию значение allocationSize равно 50, если не указывать его в аннотации.

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

Интерфейс Session

Работа с БД в Hibernate осуществляется через объект сессии типа org.hibernate.Session, который получают из экземпляра типа org.hibernate.SessionFactory. Интерфейс org.hibernate.Session является мостом между приложением и Hibernate. С помощью сессий выполняются CRUD-операции с объектами-сущностями.

Состояния объектов

Объект-сущность может находиться в одном из 3-х состояний :

transient в этом состоянии находятся заполненные экземпляры классов-сущностей. Они не подключены к сессии и поле Id у них не должно быть заполнено, иначе объект имеет состояние detached;
persistent объект в данном состоянии, иначе называемый хранимая сущность, присоединен к конкретной сессии и взаимодействует с БД. При работе с объектом данного типа в рамках транзакции все изменения объекта записываются в базу;
detached объект в данном состоянии отсоединён от сессии и может существовать или не существовать в БД.

Методы Session

Любой объект-сущность можно перевести из одного состояния в другой. Для этого в интерфейсе Session существуют следующие методы :

МетодОписание
persist (Object) Преобразование объекта из состояния transient в состояние persistent, то есть присоединение к сессии и сохранение в БД. Идентификатор @Id не должен быть определен. При сохранении сущности метод persist() сразу выполняет insert, не делая select. Если присвоить значение полю @Id объекта, то получим исключение PersistentObjectException. В этом случае Hibernate будет считать, что объект detached, т.е. существует в БД.
merge (Object) Преобразование объекта из состояний transient или detached в состояние persistent. Если сущность в состоянии transient, то метод работает аналогично persist(), т.е. генерирует для объекта новый Id, даже если он определен. Если объект в состоянии detached, то загружает его из БД и присоединяет к сессии; а при сохранении выполняет запрос update.
replicate (Object,
            ReplicationMode)
Преобразование объекта из состояний detached в состояние persistent. У объекта обязательно должен быть определен Id. Данный метод предназначен для сохранения в БД объекта с заданным Id, чего не позволяют сделать persist() и merge(). Если объект с данным Id уже существует в БД, то поведение определяется согласно правилу из перечисления org.hibernate.ReplicationMode :
    • ReplicationMode.IGNORE — в базе данных ничего не меняется;
    • ReplicationMode.OVERWRITE — объект сохраняется в базу данных вместо существующего;
    • ReplicationMode.LATEST_VERSION — в базе данных сохраняется объект последней версии;
    • ReplicationMode.EXCEPTION — генерируется исключение.
delete (Object) Удаление объекта из БД. Иными словами перевод объекта из persistent в transient. Object может быть в любом статусе, главное, чтобы было определено значение Id.
save (Object) Сохранение объекта в БД. При этом при необходимости генерируется новый Id, даже если он определен. Object может быть как в состоянии transient, так и в detached.
update (Object) Обновление объекта в БД с переводом его в состояние persistent.
saveOrUpdate (Object) Вызывается метод save() или update()
refresh (Object) Обновление detached-объекта выполнением запроса select к БД и преобразованием его в состояние persistent.
get (Object.class, id) Чтение из БД объект класса-сущности с определённым Id в статусе persistent.

Объект Session кэширует у себя загруженные объекты. При загрузке объекта из БД в первую очередь проверяется кэш. Для того, чтобы удалить объект из кэша и отсоединить от сессии, используется метод session.evict(Object). Метод session.clear() применит evict() ко всем объектам в сессии.

Пример использования метода удаления сущности представлен на странице описания языка HQL.

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

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

package net.common;

import java.util.List;
import java.util.Iterator;

import net.common.model.User;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;

public class HibernateExample
{
    private  Session  session = null;
    //---------------------------------------------------------------
    private Session createHibernateSession()
    {
        SessionFactory   sf  = null;
        ServiceRegistry  srvc = null;
        try {
            Configuration conf = new Configuration();
            conf.configure("hibernate.cfg.xml");

            srvc = new StandardServiceRegistryBuilder()
                        .applySettings(conf.getProperties()).build();
            sf = conf.buildSessionFactory(srvc);				
            session = sf.openSession();
            System.out.println("Создание сессии");
        } catch (Throwable e) {
            throw new ExceptionInInitializerError(e);
        }
        return session;
    }
    //---------------------------------------------------------------
    private void saveUser()
    {
        if (session == null)
            return;

        User user1 = new User();
        user1.setName ("Иван");
        user1.setLogin("ivan");

        User user2 = new User();
        user2.setName ("Сергей");
        user2.setLogin("serg"  );

        Transaction trans = session.beginTransaction();
        session.save(user1);
        session.save(user2);

        trans.commit();
		
        System.out.println("user1 : " + user1.toString());
        System.out.println("user2 : " + user2.toString());
    }
    //---------------------------------------------------------------
    public HibernateExample()
    {
        // Создание сессии
        session = createHibernateSession();
        if (session != null) {
            System.out.println("session is created");
            // Добавление записей в таблицу
            saveUser();
            // Закрытие сессии
            session.close();
        } else {
            System.out.println("session is not created");
        }
    }
    //---------------------------------------------------------------
    public static void main(String[] args)
    {
        new HibernateExample();
        System.exit(0);
    }
}

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

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

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


Создание сессии
session is created
Hibernate: select SEQ_USER.nextval from dual
Hibernate: insert into USERS (login, name, id) values (?, ?, ?)
Hibernate: insert into USERS (login, name, id) values (?, ?, ?)

user1 : User {id = 50, login = 'ivan, name = 'Иван'}
user2 : User {id = 51, login = 'serg, name = 'Сергей'}
 

Скачать пример

Исходный код примера в виде проекта Eclipse hibernate-sequence.zip, включающий все необходимые библиотеки hibernate, можно скачать здесь (7.61 Mб).

  Рейтинг@Mail.ru