Глава 9. Манипулирование персистентными данными

9.1. Создание персистентного объекта

Объект (экзепляр сущности) в конесксте отдельно взятой Сессии (Session), может быть транзитными (transient) или персистентным. Свежесозданные объекты всегда являются транзитными. Сессия предоставляет сервисы для сохранения (т.е. персистирования) транзитных экземпляров:

DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
Long generatedId = (Long) sess.save(fritz);
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) );

Метод save() с одним аргументом генерирует и присваевает значение уникального идентификатора объекту fritz. Метод save() с двумя аргументами пытается сохранить объект с заданным (в качестве второго параметра) идентификатором. В большинстве случаев не рекомендуется использовать метод с двумя аргументами, но такая форма вызова может использована при создании идентификаторов с бизнес-значениями. Эта форма бывает полезна в определенных ситуациях, например при использовании Hibernate для сохранения в реализации BMP Entity Beans.

Ассоциированные объекты могут сохраняться в любом порядке который вы зададите, за исключением случаев, когда существует ограничение NOT NULL на внешний ключ. При таких сохранениях никогда не возникает риска нарушения ограничений внешнего ключа. Однако возможно нарушение ограничения NOT NULL если вы сохраняете объекты в неправильном порядке.

9.2. Чтение объекта

Метод Session.load() предоставляет сервис чтения персистентного экземпляра, когда вы знаете его идентификатор. Один вариант принимает объект класса и загружает состояние в свежесозданный объект. Второй вариант замещает состояние в переданном экземпляре объекта. Форма метода, принимающего экземпляр в качестве параметра особенно полезна если вы планируете использовать Hibernate с BMP Entity Beans и она предоставлена именно для этих целей. Вы можете так же найти другие применения данной формы.

Cat fritz = (Cat) sess.load(Cat.class, generatedId);
// Необходимо обернуть примитивные идентификаторы.
long pkId = 1234;
DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
Cat cat = new DomesticCat();
// Загрузить состояние в cat
sess.load( cat, new Long(pkId) );
Set kittens = cat.getKittens();

Учтите, что load() пробросит невосстановимое исключение если в базе данных не существует записи с заданным идентификатором. Если класс был замаплен с использованием прокси, load() вернет объект который является неинициализированным прокси и не осуществит выхова к базе данных до тех пор пока вы не вызовете какой-либо метод объекта. Такой подход очень полезен, если вы хотите создаватьт ассоциации к объекту без его загрузки из базы данных.

Если вы не уверены в том, что значение с заданным идентификатором существует в базе данных, вы должны использовать метод get(), который запрашивает объект из базы немедленно и возвращает null, если объект не найден.

Cat cat = (Cat) sess.get(Cat.class, id);
if (cat==null) {
    cat = new Cat();
    sess.save(cat, id);
}
return cat;

Еще одна возможность - загрузить объект с использованием SQL SELECT ... FOR UPDATE. Подробнее смотрите в следующем разделе, где рассматривается LockMode Hibernate.

Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);

Учтите, что при выборке ассоциированного объекта или коллекции не используется FOR UPDATE.

Существует возможность перезагрузки объекта и его коллекций в любое время вызовом к методу refresh(). Это необхоимо когда используются тригерры базы данных для инициализации некоторых свойств объекта.

sess.save(cat);
sess.flush(); // принудительный SQL INSERT
sess.refresh(cat); //пересчитываем состояние (после срабатывания триггера)

9.3. Запросы

Если вы не знаете идентификаторов объектов которые вы ищите, используйте метод Session.find(). Hibernate поддерживает простой но при этом весьма мощный язык объектных запросов.

List cats = sess.find(
    "from Cat as cat where cat.birthdate = ?",
    date,
    Hibernate.DATE
);

List mates = sess.find(
    "select mate from Cat as cat join cat.mate as mate " +
    "where cat.name = ?",
    name,
    Hibernate.STRING
);

List cats = sess.find( "from Cat as cat where cat.mate.bithdate is null" );

List moreCats = sess.find(
    "from Cat as cat where " + 
    "cat.name = 'Fritz' or cat.id = ? or cat.id = ?",
    new Object[] { id1, id2 },
    new Type[] { Hibernate.LONG, Hibernate.LONG }
);

List mates = sess.find(
    "from Cat as cat where cat.mate = ?",
    izi,
    Hibernate.entity(Cat.class)
);

List problems = sess.find(
    "from GoldFish as fish " +
    "where fish.birthday > fish.deceased or fish.birthday is null"
);

Второй аргумент метода find() принимает объект или масив объектов. Третий аргумент принимает Hibernate-тип или массив типов Hibernate. Эти типы используются для связывания ? заменителей в запросах с заданными объектами (что в свою очередь отображается в входные параметры JDBC PreparedStatement). Так же как в JDBC, предпочтительней использовать этот механизм связывания вместо манипуляций со строками.

Класс Hibernate определяет набор статических методов и констант, предоставляющих доступ к большинству встроенных типов. Типы являются экземплярами класса net.sf.hibernate.type.Type.

Если ожидается что запрос вернет очень большое количество объектов, но вы не планируете использовать из все, вы можете увеличить произовдительность используя методы iterate(), которые возвращают java.util.Iterator. Этот итератор будет загружать объекты по требованию, используя идентификаторы найденные исходным SQL запросом (всего n+1 select запросов).

// выбираем идентификаторы.
Iterator iter = sess.iterate("from eg.Qux q order by q.likeliness"); 
while ( iter.hasNext() ) {
    Qux qux = (Qux) iter.next();  // выбираем объект.
    // Что-то, чего мы не можем выразить в запросе.
    if ( qux.calculateComplicatedAlgorithm() ) {
        // удаляем этот экземпляр
        iter.remove();
        // прерываем обработку
        break;
    }
}

К сожалению, интерфейс java.util.Iterator не декларирует исключений, поэтому любое SQL или Hibernate исключение возникшее в процессе испольнения методов данного итрерфейса оборачивается в LazyInitializationException (подкласс RuntimeException ).

Метод iterate() так же работает лучше в ситуациях, когда ожидается что значительная часть объектов уже загружена и закеширована в сессии, или результат запроса содержит один и тот же объект в множестве строк результирующего множества. (Когда нет закешированных данных или повторений find() работает быстрее.) Вот пример запроса который необходимо вызывать с использованием iterate():

Iterator iter = sess.iterate(
    "select customer, product " + 
    "from Customer customer, " +
    "Product product " +
    "join customer.purchases purchase " +
    "where product = purchase.product"
);

При исполнении приведенного запроса с использованием метода find() вернется очень результирующее можество (JDBC ResultSet) содержащее те же данные повторенные много раз.

Запросы Hibernate иногда возвращают кортежи объектов, в этом случае каждый кортеж возвращается как массив:

Iterator foosAndBars = sess.iterate(
    "select foo, bar from Foo foo, Bar bar " +
    "where bar.date = foo.date"
);
while ( foosAndBars.hasNext() ) {
    Object[] tuple = (Object[]) foosAndBars.next();
    Foo foo = tuple[0]; Bar bar = tuple[1];
    ....
}

9.3.1. 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.

Iterator results = sess.iterate(
        "select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
        "group by cat.color"
);
while ( results.hasNext() ) {
    Object[] row = results.next();
    Color type = (Color) row[0];
    Date oldest = (Date) row[1];
    Integer count = (Integer) row[2];
    .....
}
Iterator iter = sess.iterate(
    "select cat.type, cat.birthdate, cat.name from DomesticCat cat"
);
List list = sess.find(
    "select cat, cat.mate.name from DomesticCat cat"
);

9.3.2. 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:

Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();

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.)

<query name="eg.DomesticCat.by.name.and.minimum.weight"><![CDATA[
    from eg.DomesticCat as cat
        where cat.name = ?
        and cat.weight > ?
] ]></query>
Query q = sess.getNamedQuery("eg.DomesticCat.by.name.and.minimum.weight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();

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

//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();

9.3.3. 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.

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) );

}

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.

9.3.4. 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.

Collection blackKittens = session.filter(
    pk.getKittens(), "where this.color = ?", Color.BLACK, Hibernate.enum(Color.class)
);

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.

Collection blackKittenMates = session.filter(
    pk.getKittens(), "select this.mate where this.color = eg.Color.BLACK"
);

9.3.5. 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.

Criteria crit = session.createCriteria(Cat.class);
crit.add( Expression.eq("color", eg.Color.BLACK) );
crit.setMaxResults(10);
List cats = crit.list();

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.

9.3.6. Queries in native SQL

You may express a query in SQL, using createSQLQuery(). You must enclose SQL aliases in braces.

List cats = session.createSQLQuery(
    "SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10",
    "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",
    "cat",
    Cat.class
).list()

SQL queries may contain named and positional parameters, just like Hibernate queries.

9.4. Updating objects

9.4.1. 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:

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 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.

9.4.2. 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().

// in the first session
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);

// in a higher tier 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 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.

<id name="id" type="long" column="uid" unsaved-value="null">
    <generator class="hilo"/>
</id>

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

// 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() 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

9.4.3. Reattaching detached objects

The lock() method allows the application to reassociate an unmodified object with a new session.

//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);

9.5. 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.

sess.delete(cat);

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.

9.6. 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

  1. all entity insertions, in the same order the corresponding objects were saved using Session.save()

  2. all entity updates

  3. all collection deletions

  4. all collection element deletions, updates and insertions

  5. all collection insertions

  6. 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.

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);
// execute some queries....
sess.find("from Cat as cat left outer join cat.kittens kitten");
//change to izi is not flushed!
...
tx.commit(); //flush occurs

9.7. Ending a Session

Ending a session involves four distinct phases:

  • flush the session

  • commit the transaction

  • close the session

  • handle exceptions

9.7.1. 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.

9.7.2. Committing the database transaction

If you are using the Hibernate Transaction API, this looks like:

tx.commit(); // flush the Session and commit the transaction

If you are managing JDBC transactions yourself you should manually commit() the JDBC connection.

sess.flush();
sess.connection().commit();  // not necessary for JTA datasource

If you decide not to commit your changes:

tx.rollback();  // rollback the transaction

or:

// not necessary for JTA datasource, important otherwise
sess.connection().rollback();

If you rollback the transaction you should immediately close and discard the current session to ensure that Hibernate's internal state is consistent.

9.7.3. 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.

tx.commit();
sess.close();
sess.flush();
sess.connection().commit();  // not necessary for JTA datasource
sess.close();

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.

9.7.4. 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:

Session sess = factory.openSession();
Transaction tx = null;
try {
    tx = sess.beginTransaction();
    // do some work
    ...
    tx.commit();
}
catch (Exception e) {
    if (tx!=null) tx.rollback();
    throw e;
}
finally {
    sess.close();
}

Or, when manually managing JDBC transactions:

Session sess = factory.openSession();
try {
    // do some work
    ...
    sess.flush();
    sess.connection().commit();
}
catch (Exception e) {
    sess.connection().rollback();
    throw e;
}
finally {
    sess.close();
}

Or, when using a datasource enlisted with JTA:

UserTransaction ut = .... ;
Session sess = factory.openSession();
try {
    // do some work
    ...
    sess.flush();
}
catch (Exception e) {
    ut.setRollbackOnly();
    throw e;
}
finally {
    sess.close();
}

9.8. 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.

9.9. 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.

package net.sf.hibernate.test;

import java.io.Serializable;
import java.util.Date;
import java.util.Iterator;

import net.sf.hibernate.Interceptor;
import net.sf.hibernate.type.Type;

public class AuditInterceptor implements Interceptor, Serializable {

    private int updates;
    private int creates;

    public void onDelete(Object entity,
                         Serializable id,
                         Object[] state,
                         String[] propertyNames,
                         Type[] types) {
        // do nothing
    }

    public boolean onFlushDirty(Object entity, 
                                Serializable id, 
                                Object[] currentState,
                                Object[] previousState,
                                String[] propertyNames,
                                Type[] types) {

        if ( entity instanceof Auditable ) {
            updates++;
            for ( int i=0; i < propertyNames.length; i++ ) {
                if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
                    currentState[i] = new Date();
                    return true;
                }
            }
        }
        return false;
    }

    public boolean onLoad(Object entity, 
                          Serializable id,
                          Object[] state,
                          String[] propertyNames,
                          Type[] types) {
        return false;
    }

    public boolean onSave(Object entity,
                          Serializable id,
                          Object[] state,
                          String[] propertyNames,
                          Type[] types) {
        
        if ( entity instanceof Auditable ) {
            creates++;
            for ( int i=0; i<propertyNames.length; i++ ) {
                if ( "createTimestamp".equals( propertyNames[i] ) ) {
                    state[i] = new Date();
                    return true;
                }
            }
        }
        return false;
    }

    public void postFlush(Iterator entities) {
        System.out.println("Creations: " + creates + ", Updates: " + updates);
    }

    public void preFlush(Iterator entities) {
        updates=0;
        creates=0;
    }
    
    ......
    ......
    
}

The interceptor would be specified when a session is created.

Session session = sf.openSession( new AuditInterceptor() );

9.10. 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.

Cat fritz = ......;
Long id = (Long) catMeta.getIdentifier(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
// TODO: what about components?
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] );
    }
}