Manipulating Persistent Data
Creating a persistent object
An object (entity instance) is either transient or
persistent with respect to a particular
Session. Newly instantiated objects are, of course, transient.
The session offers services for saving (ie. persisting) transient instances:
The single-argument save() generates and assigns a unique
identifier to fritz. The two-argument form attempts to persist
pk using the given identifier. We generally discourage the use of
the two-argument form since it may be used to create primary keys with business meaning.
It is most useful in certain special situations like using Hibernate to persist a BMP
entity bean.
Associated objects may be made persistent in any order you like unless you
have a NOT NULL constraint upon a foreign key column.
There is never a risk of violating foreign key constraints. However, you
might violate a NOT NULL constraint if you
save() the objects in the wrong order.
Loading an object
The load() methods of Session give you
a way to retrieve a persistent instance if you already know its identifier.
One version takes a class object and will load the state into a newly instantiated
object. The second version allows you to supply an instance into which the state
will be loaded. The form which takes an instance is particularly useful if you plan
to use Hibernate with BMP entity beans and is provided for exactly that purpose.
You may discover other uses. (DIY instance pooling etc.)
Note that load() will throw an unrecoverable exception if there is no matching
database row. If the class is mapped with a proxy, load() returns an object
that is an uninitialized proxy and does not actually hit the database until you invoke a method of
the object. This behaviour is very useful if you wish to create an association to an object
without actually loading it from the database.
If you are not certain that a matching row exists, you should use the get()
method, which hits the database immediately and returns null if there is no matching row.
You may also load an objects using an SQL SELECT ... FOR UPDATE. See the next
section for a discussion of Hibernate LockModes.
Note that any associated instances or contained collections are not selected
FOR UPDATE.
It is possible to re-load an object and all its collections at any time, using the
refresh() method. This is useful when database triggers are used to
initialize some of the properties of the object.
Querying
If you don't know the identifier(s) of the object(s) you are looking for, use the find()
methods of Session. Hibernate supports a simple but powerful object
oriented query language.
fish.deceased or fish.birthday is null"
);]]>
The second argument to find() accepts an object
or array of objects. The third argument accepts a Hibernate type or array of
Hibernate types. These given types are used to bind the given objects to the
? query placeholders (which map to IN
parameters of a JDBC PreparedStatement). Just
as in JDBC, you should use this binding mechanism in preference to string
manipulation.
The Hibernate class defines a number of static methods
and constants, providing access to most of the built-in types, as instances
of net.sf.hibernate.type.Type.
If you expect your query to return a very large number of objects, but you
don't expect to use them all, you might get better performance from the
iterate() methods, which return a
java.util.Iterator. The iterator will load objects on
demand, using the identifiers returned by an initial SQL query (n+1 selects
total).
Unfortunately java.util.Iterator does not
declare any exceptions, so any SQL or Hibernate exceptions that occur
are wrapped in a LazyInitializationException (a
subclass of RuntimeException).
The iterate() method also performs better if
you expect that many of the objects are already loaded and cached by
the session, or if the query results contain the same objects many
times. (When no data is cached or repeated, find()
is almost always faster.) Heres an example of a query that should be
called using iterate():
Calling the previous query using find() would return a very
large JDBC ResultSet containing the same data many times.
Hibernate queries sometimes return tuples of objects, in which case each tuple
is returned as an array:
Scalar queries
Queries may specify a property of a class in the select clause.
They may even call SQL aggregate functions. Properties or aggregates are considered
"scalar" results.
The Query interface
If you need to specify bounds upon your result set (the maximum number of rows
you want to retrieve and / or the first row you want to retrieve) you should
obtain an instance of net.sf.hibernate.Query:
You may even define a named query in the mapping document. (Remember to use a
CDATA section if your query contains characters that could
be interpreted as markup.)
?
] ]>]]>
The query interface supports the use of named parameters. Named parameters
are identifiers of the form :name in the query string.
There are methods on Query for binding values to named
parameters or JDBC-style ? parameters.
Contrary to JDBC, Hibernate numbers parameters from zero. The
advantages of named parameters are:
named parameters are insensitive to the order they occur in the
query string
they may occur multiple times in the same query
they are self-documenting
Scrollable iteration
If your JDBC driver supports scrollable ResultSets, the Query
interface may be used to obtain a ScrollableResults which allows more flexible
navigation of the query results.
i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );
}]]>
The behaviour of scroll() is similar to iterate(), except that
objects may be initialized selectively by get(int), instead of an entire row being
initialized at once.
Filtering collections
A collection filter is a special type of query that may be applied to
a persistent collection or array. The query string may refer to this,
meaning the current collection element.
The returned collection is considered a bag.
Observe that filters do not require a from clause (though they may have
one if required). Filters are not limited to returning the collection elements themselves.
Criteria queries
HQL is extremely powerful but some people prefer to build queries dynamically, using an
object oriented API, rather than embedding strings in their Java code. For these people,
Hibernate provides an intuitive Criteria query API.
If you are uncomfortable with SQL-like syntax, this is perhaps the easiest way to get started
with Hibernate. This API is also more extensible than HQL. Applications might provide their
own implementations of the Criterion interface.
Queries in native SQL
You may express a query in SQL, using createSQLQuery(). You must enclose
SQL aliases in braces.
SQL queries may contain named and positional parameters, just like Hibernate queries.
Updating objects
Updating in the same Session
Transactional persistent instances (ie. objects loaded, saved, created or
queried by the Session) may be manipulated by the application
and any changes to persistent state will be persisted when the Session
is flushed (discussed later in this chapter). So the most
straightforward way to update the state of an object is to load() it,
and then manipulate it directly, while the Session is open:
Sometimes this programming model is inefficient since it would require both an SQL
SELECT (to load an object) and an SQL UPDATE
(to persist its updated state) in the same session. Therefore Hibernate offers an
alternate approach.
Updating detached objects
Many applications need to retrieve an object in one transaction, send it to the
UI layer for manipulation, then save the changes in a new transaction.
(Applications that use this kind of approach in a high-concurrency environment
usually use versioned data to ensure transaction isolation.) This approach
requires a slightly different programming model to the one described in the
last section. Hibernate supports this model by providing the
method Session.update().
If the Cat with identifier catId had already
been loaded by secondSession when the application tried to
update it, an exception would have been thrown.
The application should individually update() transient instances
reachable from the given transient instance if and only if it wants
their state also updated. (Except for lifecycle objects, discussed later.)
Hibernate users have requested a general purpose method that either saves a
transient instance by generating a new identifier or update the persistent
state associated with its current identifier. The saveOrUpdate()
method now implements this functionality.
Hibernate distinguishes "new" (unsaved) instances from "existing" (saved or
loaded in a previous session) instances by the value of their identifier
(or version, or timestamp) property. The unsaved-value
attribute of the <id> (or <version>,
or <timestamp>) mapping specifies which values should
be interpreted as representing a "new" instance.
]]>
The allowed values of unsaved-value are:
any - always save
none - always update
null - save when identifier is null (this is the default)
valid identifier value - save when identifier is null or the given value
undefined - the default for version or
timestamp, then identifier check is used
The usage and semantics of saveOrUpdate() seems to be confusing
for new users. Firstly, so long as you are not trying to use instances from one session
in another new session, you should not need to use update() or
saveOrUpdate(). Some whole applications will never use either of
these methods.
Usually update() or saveOrUpdate() are used in
the following scenario:
the application loads an object in the first session
the object is passed up to the UI tier
some modifications are made to the object
the object is passed back down to the business logic tier
the application persists these modifications by calling
update() in a second session
saveOrUpdate() does the following:
if the object is already persistent in this session, do nothing
if the object has no identifier property, save() it
if the object's identifier matches the criteria specified by
unsaved-value, save() it
if the object is versioned (version or
timestamp), then the version will take precedence
to identifier check, unless the versions
unsaved-value="undefined" (default value)
if another object associated with the session has the same
identifier, throw an exception
Reattaching detached objects
The lock() method allows the application to reassociate
an unmodified object with a new session.
Deleting persistent objects
Session.delete() will remove an object's state from the database.
Of course, your application might still hold a reference to it. So it's best to think
of delete() as making a persistent instance transient.
You may also delete many objects at once by passing a Hibernate query string to
delete().
You may now delete objects in any order you like, without risk of foreign key
constraint violations. Of course, it is still possible to violate a NOT
NULL constraint on a foreign key column by deleting objects in
the wrong order.
Flush
From time to time the Session will execute the SQL statements
needed to synchronize the JDBC connection's state with the state of objects held in
memory. This process, flush, occurs by default at the following
points
from some invocations of find() or iterate()
from net.sf.hibernate.Transaction.commit()
from Session.flush()
The SQL statements are issued in the following order
all entity insertions, in the same order the corresponding objects
were saved using Session.save()
all entity updates
all collection deletions
all collection element deletions, updates and insertions
all collection insertions
all entity deletions, in the same order the corresponding objects
were deleted using Session.delete()
(An exception is that objects using native ID generation are
inserted when they are saved.)
Except when you explicity flush(), there are absolutely no
guarantees about when the Session executes
the JDBC calls, only the order in which they are executed.
However, Hibernate does guarantee that the Session.find(..)
methods will never return stale data; nor will they return the wrong data.
It is possible to change the default behavior so that flush occurs less frequently.
The FlushMode class defines three different modes. This is most
useful in the case of "readonly" transactions, where it might be used to achieve a
(very) slight performance increase.
Ending a Session
Ending a session involves four distinct phases:
flush the session
commit the transaction
close the session
handle exceptions
Flushing the Session
If you happen to be using the Transaction API, you don't
need to worry about this step. It will be performed implicitly when the
transaction is committed. Otherwise you should call
Session.flush() to ensure that all changes are synchronized
with the database.
Committing the database transaction
If you are using the Hibernate Transaction API, this looks like:
If you are managing JDBC transactions yourself you should manually
commit() the JDBC connection.
If you decide not to commit your changes:
or:
If you rollback the transaction you should immediately close and discard the current
session to ensure that Hibernate's internal state is consistent.
Closing the Session
A call to Session.close() marks the end of a session. The main implication
of close() is that the JDBC connection will be relinquished by the session.
If you provided your own connection, close() returns a reference
to it, so you can manually close it or return it to the pool. Otherwise close()
returns it to the pool.
Exception handling
If the Session throws an exception (including
any SQLException), you should immediately
rollback the transaction, call Session.close()
and discard the Session instance. Certain
methods of Session will not
leave the session in a consistent state.
The following exception handling idiom is recommended:
Or, when manually managing JDBC transactions:
Or, when using a datasource enlisted with JTA:
Lifecyles and object graphs
To save or update all objects in a graph of associated objects, you must either
save(), saveOrUpdate() or
update() each individual object OR
map associated objects using cascade="all" or
cascade="save-update".
Likewise, to delete all objects in a graph, either
delete() each individual object OR
map associated objects using cascade="all",
cascade="all-delete-orphan" or
cascade="delete".
Recommendation:
If the child object's lifespan is bounded by the lifespan of the of the parent
object make it a lifecycle object by specifying
cascade="all".
Otherwise, save() and delete() it
explicitly from application code. If you really want to save yourself some
extra typing, use cascade="save-update" and explicit
delete().
Mapping an association (many-to-one, or collection) with cascade="all"
marks the association as a parent/child style relationship where
save/update/deletion of the parent results in save/update/deletion of the child(ren).
Futhermore, a mere reference to a child from a persistent parent will result in save / update
of the child. The metaphor is incomplete, however. A child which becomes unreferenced by its
parent is not automatically deleted, except in the case of a
<one-to-many> association mapped with
cascade="all-delete-orphan". The precise semantics of cascading operations
are as follows:
If a parent is saved, all children are passed to saveOrUpdate()
If a parent is passed to update() or saveOrUpdate(),
all children are passed to saveOrUpdate()
If a transient child becomes referenced by a persistent parent, it is passed to
saveOrUpdate()
If a parent is deleted, all children are passed to delete()
If a transient child is dereferenced by a persistent parent, nothing
special happens (the application should explicitly delete the child if
necessary) unless cascade="all-delete-orphan", in which case the
"orphaned" child is deleted.
Hibernate does not fully implement "persistence by reachability", which would imply
(inefficient) persistent garbage collection. However, due to popular demand,
Hibernate does support the notion of entities becoming persistent when referenced
by another persistent object. Associations marked
cascade="save-update" behave in this way. If you wish to use this
approach throughout your application, its easier to specify the
default-cascade attribute of the
<hibernate-mapping> element.
Interceptors
The Interceptor interface provides callbacks from the session to the
application allowing the application to inspect and / or manipulate properties of a
persistent object before it is saved, updated, deleted or loaded. One
possible use for this is to track auditing information. For example, the following
Interceptor automatically sets the createTimestamp
when an Auditable is created and updates the
lastUpdateTimestamp property when an Auditable is
updated.
The interceptor would be specified when a session is created.
Metadata API
Hibernate requires a very rich meta-level model of all entity and value types. From time
to time, this model is very useful to the application itself. For example, the application
might use Hibernate's metadata to implement a "smart" deep-copy algorithm that understands
which objects should be copied (eg. mutable value types) and which should not (eg.
immutable value types and, possibly, associated entities).
Hibernate exposes metadata via the ClassMetadata and
CollectionMetadata interfaces and the Type
hierarchy. Instances of the metadata interfaces may be obtained from the
SessionFactory.