Hibernate.orgCommunity Documentation
Table of Contents
Hibernate is a full object/relational mapping solution that not only
shields the developer from the details of the underlying database management
system, but also offers state management of objects.
This is, contrary to the management of SQL statements
in
common JDBC/SQL persistence layers, a natural object-oriented view of
persistence in Java applications.
In other words, Hibernate application developers should always think about the state of their objects, and not necessarily about the execution of SQL statements. This part is taken care of by Hibernate and is only relevant for the application developer when tuning the performance of the system.
Hibernate defines and supports the following object states:
Transient - an object is transient if it
has just been instantiated using the new
operator,
and it is not associated with a Hibernate Session
.
It has no persistent representation in the database and no identifier
value has been assigned. Transient instances will be destroyed by the
garbage collector if the application does not hold a reference
anymore. Use the Hibernate Session
to make an
object persistent (and let Hibernate take care of the SQL statements
that need to be executed for this transition).
Persistent - a persistent instance has a
representation in the database and an identifier value. It might just
have been saved or loaded, however, it is by definition in the scope
of a Session
. Hibernate will detect any changes
made to an object in persistent state and synchronize the state with
the database when the unit of work completes. Developers do not
execute manual UPDATE
statements, or
DELETE
statements when an object should be made
transient.
Detached - a detached instance is an object
that has been persistent, but its Session
has been
closed. The reference to the object is still valid, of course, and the
detached instance might even be modified in this state. A detached
instance can be reattached to a new Session
at a
later point in time, making it (and all the modifications) persistent
again. This feature enables a programming model for long running units
of work that require user think-time. We call them
application transactions, i.e., a unit of work
from the point of view of the user.
We will now discuss the states and state transitions (and the Hibernate methods that trigger a transition) in more detail.
Newly instantiated instances of a persistent class are considered transient by Hibernate. We can make a transient instance persistent by associating it with a session:
DomesticCat fritz = new DomesticCat(); fritz.setColor(Color.GINGER); fritz.setSex('M'); fritz.setName("Fritz"); Long generatedId = (Long) sess.save(fritz);
If Cat
has a generated identifier, the identifier
is generated and assigned to the cat
when
save()
is called. If Cat
has an
assigned
identifier, or a composite key, the identifier
should be assigned to the cat
instance before calling
save()
. You can also use persist()
instead of save()
, with the semantics defined in the
EJB3 early draft.
persist()
makes a transient instance
persistent. However, it does not guarantee that the identifier value
will be assigned to the persistent instance immediately, the
assignment might happen at flush time. persist()
also guarantees that it will not execute an INSERT
statement if it is called outside of transaction boundaries. This is
useful in long-running conversations with an extended
Session/persistence context.
save()
does guarantee to return an
identifier. If an INSERT has to be executed to get the identifier (
e.g. "identity" generator, not "sequence"), this INSERT happens
immediately, no matter if you are inside or outside of a transaction.
This is problematic in a long-running conversation with an extended
Session/persistence context.
Alternatively, you can assign the identifier using an overloaded
version of save()
.
DomesticCat pk = new DomesticCat(); pk.setColor(Color.TABBY); pk.setSex('F'); pk.setName("PK"); pk.setKittens( new HashSet() ); pk.addKitten(fritz); sess.save( pk, new Long(1234) );
If the object you make persistent has associated objects (e.g. the
kittens
collection in the previous example), these
objects can 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.
Usually you do not bother with this detail, as you will normally use
Hibernate's transitive persistence feature to save
the associated objects automatically. Then, even NOT
NULL
constraint violations do not occur - Hibernate will take
care of everything. Transitive persistence is discussed later in this
chapter.
The load()
methods of Session
provide a way of retrieving a persistent instance if you know its
identifier. load()
takes a class object and loads the
state into a newly instantiated instance of that class in a persistent
state.
Cat fritz = (Cat) sess.load(Cat.class, generatedId);
// you need to wrap primitive identifiers long id = 1234; DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) );
Alternatively, you can load state into a given instance:
Cat cat = new DomesticCat(); // load pk's state into cat sess.load( cat, new Long(pkId) ); Set kittens = cat.getKittens();
Be aware that load()
will throw an unrecoverable
exception if there is no matching database row. If the class is mapped
with a proxy, load()
just returns an uninitialized
proxy and does not actually hit the database until you invoke a method of
the proxy. This is useful if you wish to create an association to an
object without actually loading it from the database. It also allows
multiple instances to be loaded as a batch if
batch-size
is defined for the class mapping.
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.
Cat cat = (Cat) sess.get(Cat.class, id); if (cat==null) { cat = new Cat(); sess.save(cat, id); } return cat;
You can even load an object using an SQL SELECT ... FOR
UPDATE
, using a LockMode
. See the API
documentation for more information.
Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);
Any associated instances or contained collections will
not be selected FOR UPDATE
, unless
you decide to specify lock
or all
as
a cascade style for the association.
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.
sess.save(cat); sess.flush(); //force the SQL INSERT sess.refresh(cat); //re-read the state (after the trigger executes)
How much does Hibernate load from the database and how many SQL
SELECT
s will it use? This depends on the
fetching strategy. This is explained in Section 20.1, “Fetching strategies”.
If you do not know the identifiers of the objects you are looking for, you need a query. Hibernate supports an easy-to-use but powerful object oriented query language (HQL). For programmatic query creation, Hibernate supports a sophisticated Criteria and Example query feature (QBC and QBE). You can also express your query in the native SQL of your database, with optional support from Hibernate for result set conversion into objects.
HQL and native SQL queries are represented with an instance of
org.hibernate.Query
. This interface offers methods
for parameter binding, result set handling, and for the execution of the
actual query. You always obtain a Query
using the
current Session
:
List cats = session.createQuery( "from Cat as cat where cat.birthdate < ?") .setDate(0, date) .list(); List mothers = session.createQuery( "select mother from Cat as cat join cat.mother as mother where cat.name = ?") .setString(0, name) .list(); List kittens = session.createQuery( "from Cat as cat where cat.mother = ?") .setEntity(0, pk) .list(); Cat mother = (Cat) session.createQuery( "select cat.mother from Cat as cat where cat = ?") .setEntity(0, izi) .uniqueResult();]] Query mothersWithKittens = (Cat) session.createQuery( "select mother from Cat as mother left join fetch mother.kittens"); Set uniqueMothers = new HashSet(mothersWithKittens.list());
A query is usually executed by invoking list()
.
The result of the query will be loaded completely into a collection in
memory. Entity instances retrieved by a query are in a persistent state.
The uniqueResult()
method offers a shortcut if you
know your query will only return a single object. Queries that make use
of eager fetching of collections usually return duplicates of the root
objects, but with their collections initialized. You can filter these
duplicates through a Set
.
Occasionally, you might be able to achieve better performance by
executing the query using the iterate()
method.
This will usually be the case if you expect that the actual entity
instances returned by the query will already be in the session or
second-level cache. If they are not already cached,
iterate()
will be slower than
list()
and might require many database hits for a
simple query, usually 1 for the initial select
which only returns identifiers, and n additional
selects to initialize the actual instances.
// fetch ids Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate(); while ( iter.hasNext() ) { Qux qux = (Qux) iter.next(); // fetch the object // something we couldnt express in the query if ( qux.calculateComplicatedAlgorithm() ) { // delete the current instance iter.remove(); // dont need to process the rest break; } }
Hibernate queries sometimes return tuples of objects. Each tuple is returned as an array:
Iterator kittensAndMothers = sess.createQuery( "select kitten, mother from Cat kitten join kitten.mother mother") .list() .iterator(); while ( kittensAndMothers.hasNext() ) { Object[] tuple = (Object[]) kittensAndMothers.next(); Cat kitten = (Cat) tuple[0]; Cat mother = (Cat) tuple[1]; .... }
Queries can specify a property of a class in the
select
clause. They can even call SQL aggregate
functions. Properties or aggregates are considered "scalar" results
and not entities in persistent state.
Iterator results = sess.createQuery( "select cat.color, min(cat.birthdate), count(cat) from Cat cat " + "group by cat.color") .list() .iterator(); while ( results.hasNext() ) { Object[] row = (Object[]) results.next(); Color type = (Color) row[0]; Date oldest = (Date) row[1]; Integer count = (Integer) row[2]; ..... }
Methods on Query
are provided for binding
values to named parameters or JDBC-style ?
parameters. Contrary to JDBC, Hibernate numbers parameters
from zero. Named parameters are identifiers of the form
:name
in the query string. The advantages of named
parameters are as follows:
named parameters are insensitive to the order they occur in the query string
they can occur multiple times in the same query
they are self-documenting
//named parameter (preferred) Query q = sess.createQuery("from DomesticCat cat where cat.name = :name"); q.setString("name", "Fritz"); Iterator cats = q.iterate();
//positional parameter Query q = sess.createQuery("from DomesticCat cat where cat.name = ?"); q.setString(0, "Izi"); Iterator cats = q.iterate();
//named parameter list List names = new ArrayList(); names.add("Izi"); names.add("Fritz"); Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)"); q.setParameterList("namesList", names); List cats = q.list();
If you need to specify bounds upon your result set, that is, the
maximum number of rows you want to retrieve and/or the first row you
want to retrieve, you can use methods of the Query
interface:
Query q = sess.createQuery("from DomesticCat cat"); q.setFirstResult(20); q.setMaxResults(10); List cats = q.list();
Hibernate knows how to translate this limit query into the native SQL of your DBMS.
If your JDBC driver supports scrollable
ResultSet
s, the Query
interface
can be used to obtain a ScrollableResults
object
that allows flexible navigation of the query results.
Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " + "order by cat.name"); ScrollableResults cats = q.scroll(); if ( cats.first() ) { // find the first name on each page of an alphabetical list of cats by name firstNamesOfPages = new ArrayList(); do { String name = cats.getString(0); firstNamesOfPages.add(name); } while ( cats.scroll(PAGE_SIZE) ); // Now get the first page of cats pageOfCats = new ArrayList(); cats.beforeFirst(); int i=0; while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) ); } cats.close()
Note that an open database connection and cursor is required for
this functionality. Use
setMaxResult()
/setFirstResult()
if you need offline pagination functionality.
Queries can also be configured as so called named queries using
annotations or Hibernate mapping documents.
@NamedQuery
and @NamedQueries
can be defined at the class level as seen in Example 11.1, “Defining a named query using
@NamedQuery
” . However their
definitions are global to the session factory/entity manager factory
scope. A named query is defined by its name and the actual query
string.
Example 11.1. Defining a named query using
@NamedQuery
@Entity
@NamedQuery(name="night.moreRecentThan", query="select n from Night n where n.date >= :date")
public class Night {
...
}
public class MyDao {
doStuff() {
Query q = s.getNamedQuery("night.moreRecentThan");
q.setDate( "date", aMonthAgo );
List results = q.list();
...
}
...
}
Using a mapping document can be configured using the
<query>
node. Remember to use a
CDATA
section if your query contains characters
that could be interpreted as markup.
Example 11.2. Defining a named query using
<query>
<query name="ByNameAndMaximumWeight"><![CDATA[ from eg.DomesticCat as cat where cat.name = ? and cat.weight > ? ] ]></query>
Parameter binding and executing is done programatically as seen in Example 11.3, “Parameter binding of a named query”.
Example 11.3. Parameter binding of a named query
Query q = sess.getNamedQuery("ByNameAndMaximumWeight"); q.setString(0, name); q.setInt(1, minWeight); List cats = q.list();
The actual program code is independent of the query language that is used. You can also define native SQL queries in metadata, or migrate existing queries to Hibernate by placing them in mapping files.
Also note that a query declaration inside a
<hibernate-mapping>
element requires a global
unique name for the query, while a query declaration inside a
<class>
element is made unique automatically
by prepending the fully qualified name of the class. For example
eg.Cat.ByNameAndMaximumWeight
.
A collection filter is a special type of
query that can be applied to a persistent collection or array. The query
string can refer to this
, meaning the current
collection element.
Collection blackKittens = session.createFilter( pk.getKittens(), "where this.color = ?") .setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) ) .list() );
The returned collection is considered a bag that is a copy of the given collection. The original collection is not modified. This is contrary to the implication of the name "filter", but consistent with expected behavior.
Observe that filters do not require a from
clause, although they can have one if required. Filters are not limited
to returning the collection elements themselves.
Collection blackKittenMates = session.createFilter( pk.getKittens(), "select this.mate where this.color = eg.Color.BLACK.intValue") .list();
Even an empty filter query is useful, e.g. to load a subset of elements in a large collection:
Collection tenKittens = session.createFilter( mother.getKittens(), "") .setFirstResult(0).setMaxResults(10) .list();
HQL is extremely powerful, but some developers prefer to build
queries dynamically using an object-oriented API, rather than building
query strings. Hibernate provides an intuitive
Criteria
query API for these cases:
Criteria crit = session.createCriteria(Cat.class); crit.add( Restrictions.eq( "color", eg.Color.BLACK ) ); crit.setMaxResults(10); List cats = crit.list();
The Criteria
and the associated
Example
API are discussed in more detail in Chapter 17, Criteria Queries.
You can express a query in SQL, using
createSQLQuery()
and let Hibernate manage the mapping
from result sets to objects. You can at any time call
session.connection()
and use the JDBC
Connection
directly. If you choose to use the
Hibernate API, you must enclose SQL aliases in braces:
List cats = session.createSQLQuery("SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10") .addEntity("cat", Cat.class) .list();
List cats = session.createSQLQuery( "SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " + "{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " + "FROM CAT {cat} WHERE ROWNUM<10") .addEntity("cat", Cat.class) .list()
SQL queries can contain named and positional parameters, just like Hibernate queries. More information about native SQL queries in Hibernate can be found in Chapter 18, Native SQL.
Transactional persistent instances (i.e.
objects loaded, saved, created or queried by the
Session
) can be manipulated by the application, and any
changes to persistent state will be persisted when the
Session
is flushed. This is
discussed later in this chapter. There is no need to call a particular
method (like update()
, which has a different purpose)
to make your modifications persistent. 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:
DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) ); cat.setName("PK"); sess.flush(); // changes to cat are automatically detected and persisted
Sometimes this programming model is inefficient, as it requires in
the same session both an SQL SELECT
to load an object
and an SQL UPDATE
to persist its updated state.
Hibernate offers an alternate approach by using detached instances.
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 isolation for the "long" unit of work.
Hibernate supports this model by providing for reattachment of
detached instances using the Session.update()
or
Session.merge()
methods:
// in the first session Cat cat = (Cat) firstSession.load(Cat.class, catId); Cat potentialMate = new Cat(); firstSession.save(potentialMate); // in a higher layer of the application cat.setMate(potentialMate); // later, in a new session secondSession.update(cat); // update cat secondSession.update(mate); // update mate
If the Cat
with identifier
catId
had already been loaded by
secondSession
when the application tried to reattach
it, an exception would have been thrown.
Use update()
if you are certain that the session
does not contain an already persistent instance with the same identifier.
Use merge()
if you want to merge your modifications at
any time without consideration of the state of the session. In other
words, update()
is usually the first method you would
call in a fresh session, ensuring that the reattachment of your detached
instances is the first operation that is executed.
The application should individually update()
detached instances that are reachable from the given detached instance
only if it wants their state to be updated. This can
be automated using transitive persistence. See Section 11.11, “Transitive persistence” for more information.
The lock()
method also allows an application to
reassociate an object with a new session. However, the detached instance
has to be unmodified.
//just reassociate: sess.lock(fritz, LockMode.NONE); //do a version check, then reassociate: sess.lock(izi, LockMode.READ); //do a version check, using SELECT ... FOR UPDATE, then reassociate: sess.lock(pk, LockMode.UPGRADE);
Note that lock()
can be used with various
LockMode
s. See the API documentation and the chapter on
transaction handling for more information. Reattachment is not the only
usecase for lock()
.
Other models for long units of work are discussed in Section 13.3, “Optimistic concurrency control”.
Hibernate users have requested a general purpose method that either
saves a transient instance by generating a new identifier or
updates/reattaches the detached instances associated with its current
identifier. The saveOrUpdate()
method implements this
functionality.
// in the first session Cat cat = (Cat) firstSession.load(Cat.class, catID); // in a higher tier of the application Cat mate = new Cat(); cat.setMate(mate); // later, in a new session secondSession.saveOrUpdate(cat); // update existing state (cat has a non-null id) secondSession.saveOrUpdate(mate); // save the new instance (mate has a null id)
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()
, saveOrUpdate()
, or
merge()
. 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 another object associated with the session has the same identifier, throw an exception
if the object has no identifier property,
save()
it
if the object's identifier has the value assigned to a newly
instantiated object, save()
it
if the object is versioned by a
<version>
or
<timestamp>
, and the version property value
is the same value assigned to a newly instantiated object,
save()
it
otherwise update()
the object
and merge()
is very different:
if there is a persistent instance with the same identifier currently associated with the session, copy the state of the given object onto the persistent instance
if there is no persistent instance currently associated with the session, try to load it from the database, or create a new persistent instance
the persistent instance is returned
the given instance does not become associated with the session, it remains detached
Session.delete()
will remove an object's state
from the database. Your application, however, can still hold a reference
to a deleted object. It is best to think of delete()
as
making a persistent instance, transient.
sess.delete(cat);
You can delete objects in any order, without risk of foreign key
constraint violations. It is still possible to violate a NOT
NULL
constraint on a foreign key column by deleting objects in
the wrong order, e.g. if you delete the parent, but forget to delete the
children.
It is sometimes useful to be able to take a graph of persistent instances and make them persistent in a different datastore, without regenerating identifier values.
//retrieve a cat from one database Session session1 = factory1.openSession(); Transaction tx1 = session1.beginTransaction(); Cat cat = session1.get(Cat.class, catId); tx1.commit(); session1.close(); //reconcile with a second database Session session2 = factory2.openSession(); Transaction tx2 = session2.beginTransaction(); session2.replicate(cat, ReplicationMode.LATEST_VERSION); tx2.commit(); session2.close();
The ReplicationMode
determines how
replicate()
will deal with conflicts with existing rows
in the database:
ReplicationMode.IGNORE
: ignores the object
when there is an existing database row with the same identifier
ReplicationMode.OVERWRITE
: overwrites any
existing database row with the same identifier
ReplicationMode.EXCEPTION
: throws an
exception if there is an existing database row with the same
identifier
ReplicationMode.LATEST_VERSION
: overwrites
the row if its version number is earlier than the version number of
the object, or ignore the object otherwise
Usecases for this feature include reconciling data entered into different database instances, upgrading system configuration information during product upgrades, rolling back changes made during non-ACID transactions and more.
Sometimes 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, called
flush, occurs by default at the following
points:
before some query executions
from
org.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 explicitly 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 Query.list(..)
will never
return stale or incorrect data.
It is possible to change the default behavior so that flush occurs
less frequently. The FlushMode
class defines three
different modes: only flush at commit time when the Hibernate
Transaction
API is used, flush automatically using the
explained routine, or never flush unless flush()
is
called explicitly. The last mode is useful for long running units of work,
where a Session
is kept open and disconnected for a
long time (see Section 13.3.2, “Extended session and automatic versioning”).
sess = sf.openSession(); Transaction tx = sess.beginTransaction(); sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state Cat izi = (Cat) sess.load(Cat.class, id); izi.setName(iznizi); // might return stale data sess.find("from Cat as cat left outer join cat.kittens kitten"); // change to izi is not flushed! ... tx.commit(); // flush occurs sess.close();
During flush, an exception might occur (e.g. if a DML operation violates a constraint). Since handling exceptions involves some understanding of Hibernate's transactional behavior, we discuss it in Chapter 13, Transactions and Concurrency.
It is quite cumbersome to save, delete, or reattach individual objects, especially if you deal with a graph of associated objects. A common case is a parent/child relationship. Consider the following example:
If the children in a parent/child relationship would be value typed (e.g. a collection of addresses or strings), their life cycle would depend on the parent and no further action would be required for convenient "cascading" of state changes. When the parent is saved, the value-typed child objects are saved and when the parent is deleted, the children will be deleted, etc. This works for operations such as the removal of a child from the collection. Since value-typed objects cannot have shared references, Hibernate will detect this and delete the child from the database.
Now consider the same scenario with parent and child objects being entities, not value-types (e.g. categories and items, or parent and child cats). Entities have their own life cycle and support shared references. Removing an entity from the collection does not mean it can be deleted), and there is by default no cascading of state from one entity to any other associated entities. Hibernate does not implement persistence by reachability by default.
For each basic operation of the Hibernate session - including
persist(), merge(), saveOrUpdate(), delete(), lock(), refresh(),
evict(), replicate()
- there is a corresponding cascade style.
Respectively, the cascade styles are named create, merge,
save-update, delete, lock, refresh, evict, replicate
. If you
want an operation to be cascaded along an association, you must indicate
that in the mapping document. For example:
<one-to-one name="person" cascade="persist"/>
Cascade styles my be combined:
<one-to-one name="person" cascade="persist,delete,lock"/>
You can even use cascade="all"
to specify that
all operations should be cascaded along the
association. The default cascade="none"
specifies that
no operations are to be cascaded.
In case you are using annotatons you probably have noticed the
cascade
attribute taking an array of
CascadeType
as a value. The cascade concept in JPA
is very is similar to the transitive persistence and cascading of
operations as described above, but with slightly different semantics and
cascading types:
CascadeType.PERSIST
: cascades the persist
(create) operation to associated entities persist() is called or if
the entity is managed
CascadeType.MERGE
: cascades the merge
operation to associated entities if merge() is called or if the entity
is managed
CascadeType.REMOVE
: cascades the remove
operation to associated entities if delete() is called
CascadeType.REFRESH:
cascades the refresh
operation to associated entities if refresh() is called
CascadeType.DETACH:
cascades the detach
operation to associated entities if detach() is called
CascadeType.ALL
: all of the above
CascadeType.ALL also covers Hibernate specific operations like save-update, lock etc...
A special cascade style, delete-orphan
, applies
only to one-to-many associations, and indicates that the
delete()
operation should be applied to any child
object that is removed from the association. Using annotations there is no
CascadeType.DELETE-ORPHAN
equivalent. Instead you can
use the attribute orphanRemoval as seen in
Example 11.4, “@OneToMany
with
orphanRemoval
”. If an entity is
removed from a @OneToMany
collection or an
associated entity is dereferenced from a @OneToOne
association, this associated entity can be marked for deletion if
orphanRemoval
is set to true.
Example 11.4. @OneToMany
with
orphanRemoval
@Entity
public class Customer {
private Set<Order> orders;
@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
public Set<Order> getOrders() { return orders; }
public void setOrders(Set<Order> orders) { this.orders = orders; }
[...]
}
@Entity
public class Order { ... }
Customer customer = em.find(Customer.class, 1l);
Order order = em.find(Order.class, 1l);
customer.getOrders().remove(order); //order will be deleted by cascade
Recommendations:
It does not usually make sense to enable cascade on a
many-to-one or many-to-many association. In fact the
@ManyToOne
and @ManyToMany
don't
even offer a orphanRemoval
attribute. Cascading is
often useful for one-to-one and one-to-many associations.
If the child object's lifespan is bounded by the lifespan of the
parent object, make it a life cycle object by
specifying
cascade="all,delete-orphan"
(@OneToMany(cascade=CascadeType.ALL,
orphanRemoval=true)
).
Otherwise, you might not need cascade at all. But if you think
that you will often be working with the parent and children together
in the same transaction, and you want to save yourself some typing,
consider using
cascade="persist,merge,save-update"
.
Mapping an association (either a single valued association, or a
collection) with cascade="all"
marks the association as
a parent/child style relationship where
save/update/delete of the parent results in save/update/delete of the
child or children.
Furthermore, a mere reference to a child from a persistent parent
will result in save/update of the child. This 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="delete-orphan"
. The precise semantics of
cascading operations for a parent/child relationship are as
follows:
If a parent is passed to persist()
, all
children are passed to persist()
If a parent is passed to merge()
, all
children are passed to merge()
If a parent is passed to save()
,
update()
or saveOrUpdate()
, all
children are passed to saveOrUpdate()
If a transient or detached 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 child is dereferenced by a persistent parent,
nothing special happens - the application should
explicitly delete the child if necessary - unless
cascade="delete-orphan"
, in which case the
"orphaned" child is deleted.
Finally, note that cascading of operations can be applied to an
object graph at call time or at flush
time. All operations, if enabled, are cascaded to associated
entities reachable when the operation is executed. However,
save-update
and delete-orphan
are
transitive for all associated entities reachable during flush of the
Session
.
Hibernate requires a rich meta-level model of all entity and value types. This model can be 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 objects that should not (e.g. 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
can be obtained from the SessionFactory
.
Cat fritz = ......; ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class); Object[] propertyValues = catMeta.getPropertyValues(fritz); String[] propertyNames = catMeta.getPropertyNames(); Type[] propertyTypes = catMeta.getPropertyTypes(); // get a Map of all properties which are not collections or associations Map namedValues = new HashMap(); for ( int i=0; i<propertyNames.length; i++ ) { if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) { namedValues.put( propertyNames[i], propertyValues[i] ); } }