Chapter 8. 操作持久化数据(Manipulating Persistent Data)

8.1. 创建一个持久化对象

对象(实体的实例)对一个特定的Session来说,要么是一个瞬时(transient)对象,要么是持久化(persistent)对象。刚刚创建的对象当然是瞬时的。session则提供了把瞬时实例保存(持久化)的服务:

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生成了一个惟一标识符,并赋给这个对象。双参数的形式则使用给定的标识符保存pk。我们一般不鼓励使用双参数的形式,因为这可能会(隐含)使主键赋予业务含义。它有用的时候是在一些特殊场合下,比如使用Hibernate来持久化一个BMP实体bean.

关联的对象可以用你喜欢的任何顺序持久化,除非有外键字段具有NOT NULL的约束。决不会有外键约束冲突的危险。然而,如果在save()对象的时候用错了顺序,会触犯NOT NULL约束。

8.2. 装载对象

如果你已知某个持久化实例的标识符,Sessionload()方法让你取出它。第一种形式使用一个类对象作为参数,会把状态装载到另一个新创建的对象中去。第二个版本允许你给出一个实例,会在其中装载状态。把实例作为参数的形式在你准备把Hibernate和BMP实体bean一起使用的时候特别有用,它就是为此设计的。你也可以发现其他的用途(比如自己实现实例池等等)。

Cat fritz = (Cat) sess.load(Cat.class, generatedId);
// you need to wrap primitive identifiers
long pkId = 1234;
DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
Cat cat = new DomesticCat();
// load pk's state into cat
sess.load( cat, new Long(pkId) );
Set kittens = cat.getKittens();

请注意如果没有匹配的数据库记录,load()方法可能抛出无法恢复的exception。如果类是通过代理映射的,load()方法返回一个对象,这是一个未初始化的代理,并且直到你调用该对象的某方法时才会去访问数据库。这种行为方式在你喜欢创建一个指向某对象的关联,又不想真的从数据库中装载它的时候特别有用。

如果你不确定是否有匹配的行存在,你应该使用get()方法,它会立刻访问数据库,如果没有对应的行,返回null。

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

你可以用SQLSELECT ... FOR UPDATE装载对象。下一节有关于Hibernate LockMode的讨论。

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

注意,任何关联的实例或者包含的集合都不会被做为FOR UPDATE返回。

任何时候都可以使用refresh()方法重新装载对象和它的集合。如果你使用数据库触发器更改了对象的某些属性,这就很有用。

sess.save(cat);
sess.flush(); //force the SQL INSERT
sess.refresh(cat); //re-read the state (after the trigger executes)

8.3. Querying

如果你不能确定你要寻找的对象的标示符,请使用Sessionfind()方法。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类型或者类型的数组。这些指定的类型用来把给定的对象绑定到查询中的?占位符(实际上对应的是JDBC PreparedStatement的传入参数)。就像在JDBC中一眼,你应该优先使用这种参数绑定的方式,而非组装字符串。

Hibernate类定义了一些静态方法和常量,提供了访问大部分内置类型的手段。这些内置类型是net.sf.hibernate.type.Type的实例。

如果你知道你的查询会返回非常大量的对象,但是你不希望全部使用它们,你可以用iterate()方法获得更好的性能,它会返回一个java.util.Iterator。这个迭代器会在需要的时候装载对象,所使用的标识符来自一个前导的SQL查询。

// fetch ids
Iterator iter = sess.iterate("from eg.Qux q order by q.likeliness"); 
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;
    }
}

很不幸,java.util.Iterator没有声明任何exception。所以,发生的任何SQL或者Hibernate的exception都会被包装在一个LazyInitializationException中(它是RuntimeException的子类)。

如果你预期大部分的对象已经装载过,存在于session的缓存中了,或者查询结果包含同样的对象很多次,那么iterator()方法也会获得更好的性能。(如果没有任何数据被缓存或者重复出现,则find()总是会更快。)下面是一个应该使用iterator()调用的查询例子:

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

如果对上面的查询使用find(),会返回一个非常大的JDBCResultSet,包含很多重复的相同数据。

有时候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];
    ....
}

8.3.1. 标量查询(Scalar query)

查询可以在select子句中指定类的属性。甚至可以调用SQL的统计函数。属性或者统计值被称为“标量(scalar)”结果。

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

8.3.2. 查询接口(Query interface)

如果你需要为你的结果集设置边界(你需要获取的最大行数与/或你希望获取的第一行),你应该得到一个net.sf.hibernate.Query的实例:

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

你甚至可以在映射文档中定义命名查询。(记得用一个CDATA块把你的查询包含起来,否则在分析的时候可能引起误解。)

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

查询界面支持使用命名参数。命名参数用:name的形式在查询字符串中表示。在Query中有方法把实际参数绑定到命名参数或者JDBC风格的?参数。 和JDBC不同,Hibernate的参数从0开始计数。 使用命名参数有一些好处:

  • 命名参数不依赖于它们在查询字符串中出现的顺序

  • 在同一个查询中可以使用多次

  • 他们可读性好

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

8.3.3. 可滚动迭代(Scrollable iteration)

如果你的JDBC驱动支持可滚动的ResuleSet,Query接口可以获取一个ScrollableResults,允许你在查询结果中灵活游走。

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

}

scroll()的行为方式与iterate()很类似,除了对象可以有选择的用get(int)初始化,而非整个行都一次性被初始化。

8.3.4. 过滤集合类(Filtering collections)

集合filter是一种特殊的查询,用于一个持久化集合或者数组。查询字符串可以引用this,意为当前的数组元素。

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

返回的集合被认为是一个包(bag)。

请注意filter并不需要from 子句(当然需要的话它们也可以加上)。Filter不限定返回它们自己的集合元素。

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

8.3.5. 条件查询

HQL极为强大,但是有些人希望能够动态的使用一种面向对象API创建查询,而非在他们的Java代码中嵌入字符串。对于那部分人来说,Hibernate提供了一种直观的Criteria查询API。

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

如果你对类似于SQL的语法不是感觉很舒服的话,用这种方法开始使用Hibernate可能更容易。这种API也比HQL更可扩展。程序可以提供它们自己的Criterion接口的实现。

8.3.6. 使用本地SQL的查询

你可以使用createSQLQuery()方法,用SQL来表达查询。你必须把SQL别名用大括号包围起来。

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

和Hibernate查询一样,SQL查询也可以包含命名参数或者顺序参数。

8.4. 更改在当前session中保存或者装载的对象

持久化实例(就是通过session装载、保存、创建或者查询出的对象)可以被程序操作,所做的任何修改都会在Session清洗(flushed)的时候被持久化(参见后面的“flushing”部分)。所以最直接的更改一个对象的方法就是load()它,然后直接修改即可。

DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
cat.setName("PK");
sess.flush();  // changes to cat are automatically detected and persisted

有些时候这种编程模式显得效率不高,因为它需要在同一个session中先使用SQL SELECT(来装载对象),又有一个SQL UPDATE(来把修改的状态写回)。因此,Hibernate提供了另一种方式。

8.5. 更改在以前session中保存或者装载的对象

很多程序需要在一个事务中获取对象,然后发送到界面层去操作,用一个新的事务来保存修改。(在高同步访问的环境中使用这种方式,经常使用附带版本的数据来保证事务独立性。)这种方法需要和上一节所描述的略微不同的编程模型。Hibernate支持这种模型,因为它提供了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

如果拥有catId标识符的Cat在试图update它之前已经被secondSession装载了,会抛出一个异常。

对于给定的临时实例,当且仅当 它们触及的其他临时实例需要保存的时候, 应用程序应该对它们分别各自使用update()。(自动管理生命周期的对象(lifecycle object)除外。)

Hibernate用户曾经要求有一个通用的方法,可以为新建的临时实例生成标识符并保存,或者保存已经存在标识符的临时实例的改动。saveOrUpdate()方法就是用来提供这个功能的。Hibernate通过对象的标识符的值来分辨这是一个“新”(未保存过的)实例,还是一个“已存在”(已经保存或者从先前的session中装载的)的实例。id映射中的unsaved-value用来指定哪个值被用于表示“新”实例。

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

unsaved-value允许的取值包括:

  • any - always save 永远保存

  • none - always update 永远更新

  • null - 当标识符是空的时候保存(默认情况)

  • valid identifier value (合法的标识符值)- 当标识符是null或者这个给定的值时保存

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

saveOrUpdate()的用法和语义看来对初学者来说容易造成困惑。首先,如果你还没有试图在另一个新session中使用来自原session的实例,你根本就不需要使用update()或者saveOrUpdate()方法。有一些程序完全不需要使用这些方法。

通常,update()saveorUpdate()方法在下列情形下使用:

  • 程序在前面的session中装载了对象

  • 对象被传递到UI(界面)层

  • 对该对象进行了一些修改

  • 对象被传递回业务层

  • 应用程序在第二个session中调用update()保存修改

saveOrUpdate()完成了如下工作:

  • 如果对象已经在这个session中持久化过了,什么都不用做

  • 如果对象没有标识值,调用save()来保存它

  • 如果对象的标识值与unsaved-value中的条件匹配,调用save()来保存它

  • 如果这个session中有另外一个对象具有同样的标识符,抛出一个异常

8.6. 把在先前的session中保存或装载的对象重新与新session建立关联(reassociate)

lock()方法是用来让应用程序把一个未修改的对象重新关联到新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: 使用SELECT ... FOR UPDATE进行版本检查后关联
sess.lock(pk, LockMode.UPGRADE);

8.7. 删除持久化对象

使用Session.delete()会把对象的状态从数据库中移除。当然,你的应用程序可能仍然持有一个指向它的引用。所以,最好这样理解:delete()的用途是把一个持久化实例变成临时实例。

sess.delete(cat);

你可以通过传递给delete()一个Hibernate 查询字符串来一次性删除很多对象。

你现在可以用你喜欢的任何顺序删除对象,不用担心外键约束冲突。当然,如果你搞错了顺序,还是有可能引发在外键字段定义的NOT NULL约束冲突。

8.8. 对象图(Graphs of objects)

要保存或者更新一个对象关联图中所有的所有对象,你必须做到:

  • 保证每一个对象都执行save(), saveOrUpdate()update()方法,或者,

  • 在定义关联对象的映射时,使用cascade="all"cascade="save-update"

类似的,要删除一个关系图中的所有对象,必须:

  • 对每一个对象都执行delete(),或者

  • 在定义关联对象的映射时,使用cascade="all",cascade="all-delete-orphan"cascade="delete"

建议:

  • 如果子对象的生命期是绑定到父对象的生命期的,通过指定cascade="all"可以把它变成一个自动管理生命周期的对象(lifecycle object)

  • 否则,必须在应用程序代码中明确地执行save()delete()。如果你想少敲一些代码,可以使用cascade="sve-update",然后只需明确地delete()

8.8.1. 自动管理生命周期的对象(lifecycle object)

对一种关联(多对一,或者集合)使用cascade="all"映射,就把这种关联标记为一种父/子(parent/child)风格的关系,对父对象进行保存/更新/删除会导致对(所有)子对象的保存/更新/删除。但是这个比喻并不是特别确切。如果父对象解除了对某个子对象的关联,那这个子对象就不会被自动删除了。除非这是一个一对多的关联,并且标明了cascade="all-delete-orphan"(所有-删除-孤儿)。级联操作的精确语义在下面列出:

  • 如果父对象被保存,所有的子对象会被传递到saveOrUpdate()方法去执行

  • 如果父对象被传递到update()或者saveOrUpdate(),所有的子对象会被传递到saveOrUpdate()方法去执行

  • 如果一个临时的子对象被一个持久化的父对象引用了,它会被传递到saveOrUpdate()去执行

  • 如果父对象被删除了,所有的子对象对被传递到delete()方法执行

  • 如果临时的子对象不再被持久化的父对象引用,什么都不会发生(必要时,程序应该明确的删除这个子对象),除非声明了cascade="all-delete-orphan",在这种情况下,成为“孤儿”的子对象会被删除。

8.8.2. 通过可触及性决定持久化(Persistence by Reachability)

Hibernate还没有完全实现“通过可触及性决定持久化”,后者暗示会对垃圾收集进行(效率不高的)持久化。但是,因为很广泛的呼声,Hibernate实现了一种意见,如果一个实体被一个持久化的对象引用,它也会被持久化。注明了cascade="save-update"的关联就是按照这种思路运作的。如果你希望在你的整个程序中都贯彻这个方法,你可以在<hibernate-mapping>元素的default-cascade属性中指定这种级联方式。

8.9. 清洗(Flushing) -- 这个词很难翻译,不能使用“刷新”,因为刷新一词已经被"refresh"使用了。有什么好的建议?

每件隔一段时间,Session会执行一些必需的SQL语句来把内存中的对象和JDBC连接中的状态进行同步。这个过程被称为清洗(flush),默认会在下面的时间点执行:

  • 在某些find()或者iterate()调用的时候

  • net.sf.hibernate.Transaction.commit()的时候

  • Session.flush()的时候

涉及的SQL语句会按照下面的顺序安排:

  1. 所有对实体进行插入的语句,其顺序按照对象执行Session.save()的时间顺序

  2. 所有对实体进行更新的语句

  3. 所有进行集合删除的语句

  4. 所有对集合元素进行删除,更新或者插入的语句

  5. 所有进行集合插入的语句

  6. 所有对实体进行删除的语句,其顺序按照对象执行Session.delete()的时间顺序

(有一个例外时,如果对象使用native方式进行 ID 生成的话,它们一执行save就会被插入。)

除非你明确地发出了flush()指令,关于Session合时会执行这些JDBC调用是完全无法保证的,只能保证它们执行的前后顺序。当然,Hibernate保证,Session.find(..)绝对不会返回已经失效的数据,也不会返回错误数据。

也可以改变默认的设置,来让清洗发生的不那么频繁。FlushMode类定义了三种不同的方式。大部分情况下,它们只由当你在处理“只读”的事务时才会使用,可能会得到一些(不是那么明显的)性能提高。

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

8.10. 结束一个Session

结束一个session包括四个独立的步骤:

  • 清洗session

  • 提交事务

  • 关闭session

  • 处理异常

8.10.1. 清洗(Flush)session

如果你正在使用TransactionAPI,你就不用担心这个步骤。在事务提交的时候,隐含就会包括这一步。否则,你应该调用Session.flush()来确保你所有的修改都与数据库同步。

8.10.2. 提交事务

如果你正在使用Hibernate 的Transaction API,代码类似这样:

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

如果你自行管理JDBC事务,你应该手工对JDBC 连接执行commit()

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

如果你决定提交你的更改:

tx.rollback();  // rollback the transaction

或者:

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

8.10.3. 关闭session

调用Session.close()就标志这个session进入了尾声。close()主要的含义就是与这个session相关的JDBC连接会被放弃。

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

如果你自行管理连接,close()会返回连接的一个引用,你就可以手工把它关闭,或者返回它到连接池去。其他情况下,close()会把它返回到连接池去。

8.10.4. 处理异常

如果Session抛出了一个exception(包括任何SQLException),你应该立刻回滚这个事务,调用Session.close)()来取消这个Session实例。Session中的一些特定方式会确保session不会处于一个不稳定不完整的状态。

建议采用下面的异常处理片断:

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

如果你是手工管理JDBC事务的,用下面这段:

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

如果你是从JTA中获得数据源的:

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

8.11. 拦截器(Interceptors)

Interceptor接口提供从session到你的应用程序的回调方法,让你的程序可以观察和在持久化对象保存/更改/删除或者装载的时候操作它的属性。一种可能的用途是用来监视统计信息。比如,下面的Interceptor会自动在一个Auditable创建的时候设置其createTimestamp,并且当它被更改的时候,设置其lastUpdateTimestamp属性。

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

当session被创建的时候,就应该指定拦截器。

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

8.12. 元数据(Metadata) API

Hibernate对所有的实体和值类型都需要一个非常丰富的元级别(meta-level)模型。有时候,这个模型对应用程序本身也会非常有用。比如说,应用程序可能使用Hibernate的元数据来实现一种“智能”的深度拷贝算法,来理解哪些对象应该被拷贝(比如,可变的值类型),那些不应该(不可变的值类型和可能的被关联的实体)。

Hibernate通过ClassMetadata接口,CollectionMetadata接口和Type对象树,暴露出元数据。可以通过SessionFactory获取metadata接口的实例。

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