Глава 15. Пакетная обработка

Оглавление
  1. 15.1. Пакетная вставка
  2. 15.2. Пакетное обновление
  3. 15.3. Интерфейс StatelessSession
  4. 15.4. Операции в DML-стиле

Наивный подход к вставке 100 000 строк в базу данных с использованием 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 где-то около 50 000-й строки. Это связано с тем, что Hibernate кэширует все вновь вставленные экземпляры клиента в кэше уровня сессии. В этой главе мы покажем вам, как избежать этой проблемы.

Если вы выполняете пакетную обработку, вам необходимо включить использование пакетной обработки JDBC. Это абсолютно необходимо, если вы хотите достичь оптимальной производительности. Установите размер пакета JDBC на разумное число (например, 10-50):

hibernate.jdbc.batch_size 20

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

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

hibernate.cache.use_second_level_cache false

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

15.1. Пакетная вставка

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

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, такой же размер, как в JDBC //сбросить пакет вставок и освободить память: session.flush(); session.clear(); } }
tx.commit(); session.close();

15.2. Пакетное обновление

Для получения и обновления данных применяются те же идеи. Кроме того, вам нужно использовать 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 ) { //сбросить пакет обновлений и освободить память: session.flush(); session.clear(); } }
tx.commit(); session.close();

15.3. Интерфейс StatelessSession

В качестве альтернативы Hibernate предоставляет командный API, который может использоваться для потоковой передачи данных в базу данных и из неё в виде отдельных объектов. StatelessSession нет имеет контекста персистентности и не обеспечивает много семантик жизненного цикла более высокого уровня. В частности, сессия без состояния не реализует кэш первого уровня и не взаимодействует с кэшем второго уровня или запроса. Он не выполняет транзакционную запись write-behind или автоматическую «проверку». Операции, выполненные с использованием сессии без состояния, никогда не каскадируются на связанные экземпляры. Коллекции игнорируются сессией без состояния. Операции, выполняемые с помощью сессий без состояния обходят (bypass) модель событий и перехватчиков Hibernate. Из-за отсутствия кэша первого уровня сессии без состояния уязвимы для эффектов алиасинга (aliasing) данных. Сессия без состояния — это абстракция более низкого уровня, которая намного ближе к низлежащему JDBC.

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, возвращаемые запросом, немедленно отделяются. Они никогда не связаны с каким-либо контекстом постоянства (persistence).

Операции insert(), update() и delete(), определённые интерфейсом StatelessSession, рассматриваются как прямые операции уровня записи (row-level) базы данных. Они приводят к немедленному выполнению SQL INSERT, UPDATE или DELETE соответственно. Они имеют разную семантику операций save(), saveOrUpdate() и delete(), определённых интерфейсом сессии.

15.4. Операции в DML-стиле

Как уже обсуждалось, автоматическое и прозрачное объектно-реляционное отображение связано с управлением состоянием объекта. Состояние объекта доступно в памяти. Это означает, что манипулирование данными непосредственно в базе данных (с использованием языка манипулирования данными SQL (DML): инструкции INSERT, UPDATE или DELETE) не влияют на состояние в памяти. Тем не менее, Hibernate предоставляет методы для массового выполнения инструкций DML SQL-стиля, которые выполняются с помощью языка запросов Hibernate (глава 16. HQL: язык запросов Hibernate).

Псевдо-синтаксис для операторов UPDATE и DELETE: (UPDATE |  DELETE) FROM ? имя_сущности (WHERE where-условие) ?.

Некоторые моменты:

В качестве примера, чтобы выполнить HQL UPDATE, используйте метод Query.executeUpdate(). Этот метод назван так для тех, кто знаком с JDBC PreparedStatement.executeUpdate():

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

В соответствии со спецификацией EJB3 операторы HQL UPDATE по умолчанию не влияют на номер версии или на свойство Timestamp для затронутых сущностей. Тем не менее, вы можете заставить Hibernate сбросить значения свойств version или timestamp с помощью versioned update. Это достигается добавлением ключевого слова VERSIONED после ключевого слова UPDATE.

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

Пользовательские типы версий, org.hibernate.usertype.UserVersionType, не допускаются в сочетании с инструкцией update versioned.

Чтобы выполнить HQL DELETE, используйте тот же метод Query.executeUpdate():

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlDelete = "delete Customer c where c.name = :oldName"; // или String hqlDelete = "delete Customer where name = :oldName"; int deletedEntities = s.createQuery( hqlDelete ) .setString( "oldName", oldName ) .executeUpdate(); tx.commit(); session.close();

Значение int, возвращаемое методом Query.executeUpdate(), указывает количество сущностей, выполняемых операцией. Это может или не может коррелировать с количеством записей, созданных в базе данных. Операция большого объема HQL может привести к выполнению нескольких действительных операторов SQL (например, для объединённого подкласса (joined-subclass)). Возвращаемое число указывает количество фактических сущностей, на которые влияет инструкция. Возвращаясь к примеру объединённого подкласса, удаление в отношении одного из подклассов может фактически привести к удалению не только таблицы, на которую этот подкласс отображён, но также «корневой» таблицы и потенциально подключенных подклассов далее вниз по иерархии наследования.

Псевдо-синтаксис для операторов INSERT: INSERT INTO имя_сущности список_свойств select-инструкция. Некоторые моменты:

Ниже приведен пример выполнения инструкции 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 = s.createQuery( hqlInsert ).executeUpdate(); tx.commit(); session.close();