Документация разработчика Hibernate – Глава IV. Пакетная обработка

Представляю вашему вниманию перевод четвертой главы официальной документации Hibernate.

Перевод статьи актуален для версии Hibernate 4.2.19.Final

Следующая глава — Документация разработчика Hibernate – Глава V. Блокировки

Содержание
 4.1. Пакетные вставки (Batch inserts)
 4.2. Пакетные обновления (Batch updates)
 4.3. StatelessSession
 4.4. Язык запросов Hibernate для DML
   4.4.1. HQL для UPDATE и DELETE
   4.4.2. HQL-синтаксис для INSERT
Следующий пример демонстрирует анти-паттерн пакетных вставок.

Пример 4.1. Наивный способ вставки 100000 строк с помощью Hibernate

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(.....);
session.save(customer);
}
tx.commit();
session.close();


Этот код упадет с исключением OutOfMemoryException после обработки около 50000 строк на большинстве систем. Причина в том, что Hibernate кэширует все недавно созданные экземпляры Customer в кэше уровня сессии. Есть несколько путей избежания подобной проблемы.
Перед началом работы с пакетной обработкой, разрешите её использование в JDBC. Для разрешения пакетной обработки, проставьте значение между 10 и 50 в свойство hibernate.jdbc.batch_size.

Важно

Hibernate выключает пакетные вставки на уровне JDBC прозрачно, если вы используете генератор идентификаторов


Если данный подход не приемлем, вы можете отключить кэш второго уровня (second-level cache), проставив свойство hibernate.cache.use_second_level_cache в false

4.1. Пакетные вставки


Когда вы делаете новые объекты персистентными, используйте методы сессии flush() и clear() для контроля размера кэша первого уровня.

Пример 4.2. Сброс(Flushing) и очистка сессии Session

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
   
for ( int i=0; i<100000; i++ ) {
    Customer customer = new Customer(.....);
    session.save(customer);
    if ( i % 20 == 0 ) { //20, same as the JDBC batch size
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}
   
tx.commit();
session.close();


4.2. Пакетные обновления


Используйте регулярно flush() и clear(), когда вы извлекаете и изменяете данные. В дополнение, используйте метод scroll() для получения преимуществ работы с курсорами БД для запросов, возвращающих большое количество строк.

Пример 4.3. Использование scroll()

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
   
ScrollableResults customers = session.getNamedQuery("GetCustomers")
    .setCacheMode(CacheMode.IGNORE)
    .scroll(ScrollMode.FORWARD_ONLY);
int count=0;
while ( customers.next() ) {
    Customer customer = (Customer) customers.get(0);
    customer.updateStuff(...);
    if ( ++count % 20 == 0 ) {
        //flush a batch of updates and release memory:
        session.flush();
        session.clear();
    }
}
   
tx.commit();
session.close()


4.3. StatelessSession


StatelessSession – командно-ориентированный API, предоставляемый Hibernate. Используйте его для потоковой передачи данных в базу и из нее в форме отсоединенных (detached) объектов. StatelessSession не имеет ассоциированного persistence-контекста и не предоставляет большую часть высокоуровневой семантики.

Особенности, не предоставляемые StatelessSession:

  • Кэш первого уровня
  • Взаимодействия с кэшем первого уровня или кэшем запросов
  • Транзакционный write-behind или автоматические проверки на модификацию (dirty checking)


Ограничения StatelessSession:

  • Операции, осуществляемые сессией StatelessSession, нe осуществляются каскадно до связанных сущностей
  • Коллекции игнорируются
  • Операции, осуществляемые через StatelessSession, минуют событийную модель Hibernate и его перехватчики (interceptors).
  • Из-за отсутствия кэша первого уровня, сессии StatelessSession уязвимы к появлению эффектов множественных псевдонимов данных (data aliasing effects).
  • Сессия StatelessSession – низкоуровневая абстракция, которая ближе к нижележащему JDBC.


Пример 4.4. Использование StatelessSession

StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();
   
ScrollableResults customers = session.getNamedQuery("GetCustomers")
    .scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() ) {
    Customer customer = (Customer) customers.get(0);
    customer.updateStuff(...);
    session.update(customer);
}
   
tx.commit();
session.close();


Объекты Customer, возвращенные запросом, будут отсоединены немедленно. Они не будут ассоциированы с каким-либо контекстом персистентности.

Операции insert(), update(), и delete(), определенные в интерфейсе StatelessSession работают напрямую со строками таблиц. Они приводят к немедленному выполнению соотвествующих SQL-операций, т.к имеют другую семантику, нежели методы save(), saveOrUpdate(), и delete(), определенные в интерфейсе Session.

4.4. Язык запросов Hibernate для DML


DML, или Data Markup Language, имеет отношение к таким SQL-выражениям, как INSERT, UPDATE, и DELETE. Hibernate предоставляет методы для группового выполнения SQL-подобных DML операций, в форме HQL(Hibernate Query Language).

4.4.1 HQL для UPDATE и DELETE

Пример 4.5. Псевдо-синтаксис для UPDATE и DELETE выражений с использованием HQL

( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?


Суффикс? означает необязательный параметр. FROM и WHERE оба необязательны.
FROM может указывать только на одну сущность, которая может иметь псевдоним. Если имя сущности имеет псевдоним, любые ссылки на свойства должны быть ограничены (qualified) этим псевдонимом. Если имя сущности не имеет псевдонима, в таком случае ссылки не должны ограничиваться (qualified).

Join’ы, неявные или явные, запрещены в групповых HQL-запросах. Вы можете использовать подзапросы в выражении WHERE, и подзапросы сами по себе могут содержать join’ы.

Пример 4.6. Выполнение HQL UPDATE, используя метод Query.executeUpdate()

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
// or String hqlUpdate = "update Customer set name = :newName where name = :oldName";
int updatedEntities = session.createQuery( hqlUpdate )
        .setString( "newName", newName )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();


В соответствии со спецификацией EJB3, выражения HQL UPDATE, по-умолчанию не затрагивают значения версии или временной метки (timestamp) у модифицированных сущностей. Вы можете использовать апдейт c поддержкой версий, чтобы заставить Hibernate сбросить версию или временную метку, добавив ключевое слово VERSIONED после UPDATE.

Пример 4.7. Апдейт версии временной метки

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName";
int updatedEntities = session.createQuery( hqlUpdate )
        .setString( "newName", newName )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();


Важно

Если вы используете выражение VERSIONED, вы не можете использовать пользовательские типы версий, которые используют класс org.hibernate.usertype.UserVersionType


Пример 4.8. Выражение HQL DELETE

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

String hqlDelete = "delete Customer c where c.name = :oldName";
// or String hqlDelete = "delete Customer where name = :oldName";
int deletedEntities = session.createQuery( hqlDelete )
        .setString( "oldName", oldName )
        .executeUpdate();
tx.commit();
session.close();


Метод Query.executeUpdate() возвращает значение типа int, которое показывают число затронутых операцией сущностей. Данное число не обязательно должно коррелировать с числом строк, обновленных в базе данных. Групповая операция в HQL может представлять из себя несколько SQL-выражений, например для соединяемого подкласса (joined-subclass). В примере с соединяемым подклассом, DELETE для одного из подклассов может на самом деле привести к удалениям в таблицах, нижележащих под join’ом или ниже по иерархии наследования.

4.4.2 HQL-синтаксис для INSERT


Пример 4.9. Псевдо-синтаксис для выражений INSERT

INSERT INTO EntityName properties_list select_statement


Поддерживается только форма INSERT INTO… SELECT… Вы не можете указать явные значения для вставки.

properties_list это аналог для спецификации колонок в SQL-выражении INSERT. Для сущностей, вовлеченных в наследование маппинга (mapped inheritance), вы можете указывать свойства, прямо указанные в самом классе, но не из подклассов или родительского класса. Другими словами, выражение INSERT, по своей сути не полиморфно.

select_statement может быть любым валидным HQL запросом на выборку (select), но возвращаемые типы должны соответствовать типам, ожидаемым в INSERT. Hibernate проверяет возвращаемые типы во время компиляции, не ожидая, пока их проверит СУБД. Проблемы могут идти от типов Hibernate, которые скорее эквивалентны, но не равны. Один из примеров этого заключается в несоответствии свойства, определенного как org.hibernate.type.DateType и свойства, определенного как org.hibernate.type.TimestampType, даже если БД не делает их различения, или способна сама сделать конверсию типов.

Если свойство id не указано в properties_list, Hibernate генерирует значение автоматически. Автоматическая генерация доступна только если вы используете id-генераторы. Иначе, Hibernate кинет исключение во время разбора. Доступные для баз данных генераторы это org.hibernate.id.SequenceGenerator и его подклассы, и объекты, реализующие org.hibernate.id.PostInsertIdentifierGenerator. Наиболее примечательное исключение это org.hibernate.id.TableHiLoGenerator, который не предоставляет какого-либо способа получения его значений.
Для свойств, спроецированных как версия или временная метка, выражение insert предоставляет вам два варианта. Вы можете или указать свойство в properties_list, в таком случае значение возьмется из сопутствующего select-выражения, или убрать из properties_list, в таком случае будет использоваться начальное (seed) значение, определенное в org.hibernate.type.VersionType.

Пример 4.10. Выражение HQL INSERT

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ...";
int createdEntities = session.createQuery( hqlInsert )
        .executeUpdate();
tx.commit();
session.close();

© Habrahabr.ru