-
Language:
English
-
Language:
English
Red Hat Training
A Red Hat training course is available for Red Hat JBoss Web Server
10.7. Other Object Operations
10.7.1. Automatic State Detection
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
10.7.2. Deleting Persistent Objects
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.
10.7.3. Replicating an Object Between Two Datastores
It is sometimes useful to be able to take the state 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 = (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 identifierReplicationMode.OVERWRITE
: overwrites any existing database row with the same identifierReplicationMode.EXCEPTION
: throws an exception if there is an existing database row with the same identifierReplicationMode.LATEST_VERSION
: overwrites the row if its version number is earlier than the version number of the object, or ignore the object otherwise
Use cases 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.
10.7.4. Flushing the Session
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 the Section 11.4.3, “Extended Session and Automatic Versioning” for further information).
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.createQuery("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 11, Transactions and Concurrency.
10.7.5. Transitive Persistence
It is quite cumbersome to save, delete, or reattach individual objects, especially if you deal with a set 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.
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.
Recommendations:
- It does not usually make sense to enable cascade on a
<many-to-one>
or<many-to-many>
association. Cascade 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"
. - 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 topersist()
- If a parent is passed to
merge()
, all children are passed tomerge()
- If a parent is passed to
save()
,update()
orsaveOrUpdate()
, all children are passed tosaveOrUpdate()
- 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
.
10.7.6. Using Metadata
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, EntityMode.POJO); 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] ); } }