hibernate-orm/doc/reference/en/modules/batch.xml

348 lines
16 KiB
XML
Executable File

<chapter id="batch">
<title>Batch processing</title>
<para>
A naive approach to inserting 100 000 rows in the database using Hibernate might
look like this:
</para>
<programlisting><![CDATA[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();]]></programlisting>
<para>
This would fall over with an <literal>OutOfMemoryException</literal> somewhere
around the 50 000th row. That's because Hibernate caches all the newly inserted
<literal>Customer</literal> instances in the session-level cache.
</para>
<para>
In this chapter we'll show you how to avoid this problem. First, however, if you
are doing batch processing, it is absolutely critical that you enable the use of
JDBC batching, if you intend to achieve reasonable performance. Set the JDBC batch
size to a reasonable number (say, 10-50):
</para>
<programlisting><![CDATA[hibernate.jdbc.batch_size 20]]></programlisting>
<para id="disablebatching" revision="1">
Note that Hibernate disables insert batching at the JDBC level transparently if you
use an <literal>identiy</literal> identifier generator.
</para>
<para>
You also might like to do this kind of work in a process where interaction with
the second-level cache is completely disabled:
</para>
<programlisting><![CDATA[hibernate.cache.use_second_level_cache false]]></programlisting>
<para>
However, this is not absolutely necessary, since we can explicitly set the
<literal>CacheMode</literal> to disable interaction with the second-level cache.
</para>
<sect1 id="batch-inserts">
<title>Batch inserts</title>
<para>
When making new objects persistent, you must <literal>flush()</literal> and
then <literal>clear()</literal> the session regularly, to control the size of
the first-level cache.
</para>
<programlisting><![CDATA[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();]]></programlisting>
</sect1>
<sect1 id="batch-update" >
<title>Batch updates</title>
<para>
For retrieving and updating data the same ideas apply. In addition, you need to
use <literal>scroll()</literal> to take advantage of server-side cursors for
queries that return many rows of data.
</para>
<programlisting><![CDATA[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();]]></programlisting>
</sect1>
<sect1 id="batch-statelesssession">
<title>The StatelessSession interface</title>
<para>
Alternatively, Hibernate provides a command-oriented API that may be used for
streaming data to and from the database in the form of detached objects. A
<literal>StatelessSession</literal> has no persistence context associated
with it and does not provide many of the higher-level lifecycle semantics.
In particular, a stateless session does not implement a first-level cache nor
interact with any second-level or query cache. It does not implement
transactional write-behind or automatic dirty checking. Operations performed
using a stateless session do not ever cascade to associated instances. Collections
are ignored by a stateless session. Operations performed via a stateless session
bypass Hibernate's event model and interceptors. Stateless sessions are vulnerable
to data aliasing effects, due to the lack of a first-level cache. A stateless
session is a lower-level abstraction, much closer to the underlying JDBC.
</para>
<programlisting><![CDATA[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();]]></programlisting>
<para>
Note that in this code example, the <literal>Customer</literal> instances returned
by the query are immediately detached. They are never associated with any persistence
context.
</para>
<para>
The <literal>insert(), update()</literal> and <literal>delete()</literal> operations
defined by the <literal>StatelessSession</literal> interface are considered to be
direct database row-level operations, which result in immediate execution of a SQL
<literal>INSERT, UPDATE</literal> or <literal>DELETE</literal> respectively. Thus,
they have very different semantics to the <literal>save(), saveOrUpdate()</literal>
and <literal>delete()</literal> operations defined by the <literal>Session</literal>
interface.
</para>
</sect1>
<sect1 id="batch-direct" revision="3">
<title>DML-style operations</title>
<para>
As already discussed, automatic and transparent object/relational mapping is concerned
with the management of object state. This implies that the object state is available
in memory, hence manipulating (using the SQL <literal>Data Manipulation Language</literal>
(DML) statements: <literal>INSERT</literal>, <literal>UPDATE</literal>, <literal>DELETE</literal>)
data directly in the database will not affect in-memory state. However, Hibernate provides methods
for bulk SQL-style DML statement execution which are performed through the
Hibernate Query Language (<xref linkend="queryhql">HQL</xref>).
</para>
<para>
The pseudo-syntax for <literal>UPDATE</literal> and <literal>DELETE</literal> statements
is: <literal>( UPDATE | DELETE ) FROM? EntityName (WHERE where_conditions)?</literal>. Some
points to note:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
In the from-clause, the FROM keyword is optional
</para>
</listitem>
<listitem>
<para>
There can only be a single entity named in the from-clause; it can optionally be
aliased. If the entity name is aliased, then any property references must
be qualified using that alias; if the entity name is not aliased, then it is
illegal for any property references to be qualified.
</para>
</listitem>
<listitem>
<para>
No <xref linkend="queryhql-joins-forms">joins</xref> (either implicit or explicit)
can be specified in a bulk HQL query. Sub-queries may be used in the where-clause;
the subqueries, themselves, may contain joins.
</para>
</listitem>
<listitem>
<para>
The where-clause is also optional.
</para>
</listitem>
</itemizedlist>
<para>
As an example, to execute an HQL <literal>UPDATE</literal>, use the
<literal>Query.executeUpdate()</literal> method (the method is named for
those familiar with JDBC's <literal>PreparedStatement.executeUpdate()</literal>):
</para>
<programlisting><![CDATA[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 = s.createQuery( hqlUpdate )
.setString( "newName", newName )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();]]></programlisting>
<para>
HQL <literal>UPDATE</literal> statements, by default do not effect the
<xref linkend="mapping-declaration-version">version</xref>
or the <xref linkend="mapping-declaration-timestamp">timestamp</xref> property values
for the affected entities; this is in keeping with the EJB3 specification. However,
you can force Hibernate to properly reset the <literal>version</literal> or
<literal>timestamp</literal> property values through the use of a <literal>versioned update</literal>.
This is achieved by adding the <literal>VERSIONED</literal> keyword after the <literal>UPDATE</literal>
keyword.
</para>
<programlisting><![CDATA[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();]]></programlisting>
<para>
Note that custom version types (<literal>org.hibernate.usertype.UserVersionType</literal>)
are not allowed in conjunction with a <literal>update versioned</literal> statement.
</para>
<para>
To execute an HQL <literal>DELETE</literal>, use the same <literal>Query.executeUpdate()</literal>
method:
</para>
<programlisting><![CDATA[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 = s.createQuery( hqlDelete )
.setString( "oldName", oldName )
.executeUpdate();
tx.commit();
session.close();]]></programlisting>
<para>
The <literal>int</literal> value returned by the <literal>Query.executeUpdate()</literal>
method indicate the number of entities effected by the operation. Consider this may or may not
correlate to the number of rows effected in the database. An HQL bulk operation might result in
multiple actual SQL statements being executed, for joined-subclass, for example. The returned
number indicates the number of actual entities affected by the statement. Going back to the
example of joined-subclass, a delete against one of the subclasses may actually result
in deletes against not just the table to which that subclass is mapped, but also the "root"
table and potentially joined-subclass tables further down the inheritence hierarchy.
</para>
<para>
The pseudo-syntax for <literal>INSERT</literal> statements is:
<literal>INSERT INTO EntityName properties_list select_statement</literal>. Some
points to note:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
Only the INSERT INTO ... SELECT ... form is supported; not the INSERT INTO ... VALUES ... form.
</para>
<para>
The properties_list is analogous to the <literal>column speficiation</literal>
in the SQL <literal>INSERT</literal> statement. For entities involved in mapped
inheritence, only properties directly defined on that given class-level can be
used in the properties_list. Superclass properties are not allowed; and subclass
properties do not make sense. In other words, <literal>INSERT</literal>
statements are inherently non-polymorphic.
</para>
</listitem>
<listitem>
<para>
select_statement can be any valid HQL select query, with the caveat that the return types
must match the types expected by the insert. Currently, this is checked during query
compilation rather than allowing the check to relegate to the database. Note however
that this might cause problems between Hibernate <literal>Type</literal>s which are
<emphasis>equivalent</emphasis> as opposed to <emphasis>equal</emphasis>. This might cause
issues with mismatches between a property defined as a <literal>org.hibernate.type.DateType</literal>
and a property defined as a <literal>org.hibernate.type.TimestampType</literal>, even though the
database might not make a distinction or might be able to handle the conversion.
</para>
</listitem>
<listitem>
<para>
For the id property, the insert statement gives you two options. You can either
explicitly specify the id property in the properties_list (in which case its value
is taken from the corresponding select expression) or omit it from the properties_list
(in which case a generated value is used). This later option is only available when
using id generators that operate in the database; attempting to use this option with
any "in memory" type generators will cause an exception during parsing. Note that
for the purposes of this discussion, in-database generators are considered to be
<literal>org.hibernate.id.SequenceGenerator</literal> (and its subclasses) and
any implementors of <literal>org.hibernate.id.PostInsertIdentifierGenerator</literal>.
The most notable exception here is <literal>org.hibernate.id.TableHiLoGenerator</literal>,
which cannot be used because it does not expose a selectable way to get its values.
</para>
</listitem>
<listitem>
<para>
For properties mapped as either <literal>version</literal> or <literal>timestamp</literal>,
the insert statement gives you two options. You can either specify the property in the
properties_list (in which case its value is taken from the corresponding select expressions)
or omit it from the properties_list (in which case the <literal>seed value</literal> defined
by the <literal>org.hibernate.type.VersionType</literal> is used).
</para>
</listitem>
</itemizedlist>
<para>
An example HQL <literal>INSERT</literal> statement execution:
</para>
<programlisting><![CDATA[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();]]></programlisting>
</sect1>
</chapter>