1.与触发器协同工作
若Session的save()、update()、saveOrUpdate()或delete()方法会触发一个触发器,而这个触发器的行为会导致Session的缓存的数据与数据库不一致,解决办法是在执行完该操作后,立即调用Session的flush()和refresh()方法,迫使Session的缓存与数据库同步。
tx = session.beginTransaction();
session.save(customer);
session.flush();
session.refresh(customer);
tx.commit();
为避免Session的update()方法盲目的激发触发器,可以将<class>元素的select-before-update属性设为true:
<class name="mypack.Customer" table="CUSTOMERS" select-before-update="true">
...
</class>
2.利用拦截器(Interceptor)生成审计日志
可以把拦截器看成是持久化层的触发器,当Session执行save()、update()、saveOrUpdate()、delete()及flush()方法时,就会调用拦截器的相关方法。
用户定义的拦截器必须实现Interceptor接口,该接口主要方法为:
findDirty():查找Session中的脏对象,flush()方法调用该方法;
instantiate(Class clazz,Serializable id):创建实体类的实例;
isUnsaved(Object entity):Session的saveOrUpdate()方法调用该方法;
onDelete():Session删除一个对象之前调用该方法;
onFlushDirty():Session的flush方法检查到脏数据时调用该方法;
onLoad():Session初始化一个持久化对象时调用该方法;
onSave():Session保存一个对象之前调用该方法;
postFlush(Iterator entities):Session的flush()方法执行完所有SQL语句后调用该方法;
preFlush(Iterator entities):Session执行flush()方法之前调用该方法。
Hibernate还提供了Interceptor接口的一个实现类EmptyInterceptor,用户自定义的拦截器也可以扩展EmptyInterceptor类。
3.Hibernate的事件处理机制
Hibernate3的核心处理模块采用“事件/监听器”设计模式。
在org.hibernate.event包中提供了与Session的各个方法对应的事件类及监听器接口,org.hibernate.event.def包中提供了各个监听器接口的默认实现类。
Hibernate允许用户创建客户化监听器 ,来替代或扩展Hibernate默认事件处理行为:
(1)创建客户化监听器
客户化监听器可以直接实现特定的监听器接口,或者继承Hibernate提供的监听器接口的基础实现类(如AbstractSaveEventListener),或者继承Hibernate提供的监听器接口的默认实现类(如DefaultSaveEventListener)。
(2)注册客户化监听器
方式一:在Hibernate的配置文件中静态注册
<event type="load">
<listener class="mypack.MyLoadListener1" />
<listener class="mypack.MyLoadListener2" />
</event>
方式二:在程序中动态注册
Configuration config = new Configuration();
LoadEventListener[] listenerStack = {new MyLoadListener1(), new MyLoadListener2()};
config.getEventListeners90.setLoadEventListeners(listenerStack);
4.批量处理数据
4.1.通过Session来进行批量操作
在处理完一个对象或一小批对象后,立即调用flush()方法清理缓存,然后再调用clear()方法清空缓存。
需要注意的是:
(1)需要在Hibernate配置文件中设置JDBC单次批量处理的数目
hibernate.jdbc.batch_size = 20
应保证每次向数据库发送的批量SQL语句数目与这个batch size属性一致;
(2)如果对象采用“identity”标识符生成器,Hibernate无法再JDBC层进行批量插入操作;
(3)进行批量操作时,建议关闭Hibernate的第二级缓存(默认是关闭的)。
一、批量插入
向数据库中插入十万条CUSTOMERS记录,单次批量插入20条记录:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for(int i=0; i<100000; i++){
Customer customer = new Customer(...);
session.save(customer);
if(i%20 == 0){
session.flush();
session.clear();
}
}
tx.commit();
session.close();
为保证程序顺利运行,需要遵循以下约束:
(1)hibernate.jdbc.batch_size = 20;
(2)关闭第二级缓存;
(3)Customer对象的标识符生成器不能为“identity”。
二、批量更新数据
使用可滚动的结果集ScrollableResults,Query的scroll()方法返回此对象。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers = session.createQuery("from Customer").scroll(ScrollMode.FORWARD_ONLY);
int count = 0;
while(customer.next()){
Customer customer = (Customer)customers.get(0);
customer.setAge(customer.getAge()+1);
if(++count % 20 ==0){
session.flush();
session.clear();
}
}
tx.commit();
session.close();
为保证程序顺利运行,需要遵循以下约束:
(1)hibernate.jdbc.batch_size = 20;
(2)关闭第二级缓存;
如果在配置文件中启用了第二级缓存,可以采用以下方式忽略第二级缓存:ScrollableResults customers = session.createQuery("from Customer") .setCatchMode(CatchMode.IGNORE)
.scroll(ScrollMode.FORWARD_ONLY);
4.2.通过StatelessSession来进行批量操作
进行批量操作时,把大量对象放在Session缓存中会消耗大量内存空间,作为替代方案,可采用无状态StatelessSession来进行批量操作。
Session session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();
ScrollableResults customers = session.createQuery("from Customer").scroll(ScrollMode.FORWARD_ONLY);
while(customer.next()){
Customer customer = (Customer)customers.get(0);
customer.setAge(customer.getAge()+1);
session.update(customer); //立即执行update
}
tx.commit();
session.close();
StatelessSession与Session的区别:
(1)StatelessSession没有缓存,通过StatelessSession加载、保存或更新后的对象都处于游离状态;
(2)StatelessSession不会与Hibernate的第二级缓存交互;
(3)当调用StatelessSession的save()、update()或delete()方法时,会立即执行相应的SQL语句,不会仅是计划执行;
(4)StatelessSession不会对所加载的对象自动进行脏检查;
(5)StatelessSession不会对所关联的对象进行任何级联操作;
(6)StatelessSession所做的操作可以被Interceptor拦截器捕获到,但会被Hibernate事件处理系统忽略;
(7)通过同一个StatelessSession对象两次加载OID相同的对象时,会得到两个具有不用内存地址的对象。
4.3.通过HQL来进行批量操作
HQL批量操作实际上直接在数据库中完成,所处理的数据不会被保存在Session的缓存中,因此不会占用内存空间。
一、批量更新数据
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName";
int updatedEntities = session.createQuery(hqlUpdate)
.setString("newName","Mike")
.setString("oldName","Tom")
.executeUpdate();
tx.commit();
session.close();
二、批量删除数据
Session的delete()方法一次只能删除一个对象,不合适用来进行批量删除操作。以下用HQL来批量删除:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlDelete = "deleteCustomer c where c.name = :oldName";
int updatedEntities = session.createQuery(hqlDelete)
.setString("oldName","Tom")
.executeUpdate();
tx.commit();
session.close();
三、批量插入数据
HQL只支持"insert into ... select ..." 形式的插入语句,而不支持"insert into ... values ... "形式的插入语句。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
String hqlInsert = "insert into Account(id,name) "+
"select c.id, c.name from Customer c where c.id > 1";
int createdEntities = session.createQuery(hqlInsert) .executeUpdate();
tx.commit();
session.close();
4.4.直接通过JDBC API来进行批量操作
通过JDBC API来执行SQL insert、update和delete语句时,SQL语句中涉及到的数据不会被加载到内存中,因此不会占用内存空间。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Connection con = session.connection();
PreparedStatement stmt = con.prepareStatement("update CUSTOMERS set AGE = AGE+1 where AGE > 0");
stmt.executeUpdate();
tx.commit();
session.close();
Hibernate3及之后不提倡使用Session的connection()方法,使用Work()接口表示直接通过JDBC API来进行数据库操作。
Session的doWork(Work work)方法用于执行Work对象指定的操作,即调用Work对象的execute()方法。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Work work = new Work(){
public void execute(Connection connection) throws SQLException{
PreparedStatement stmt = connection.prepareStatement("update CUSTOMERS set AGE = AGE+1 where AGE > 0");
stmt.executeUpdate();
}
};
session.doWork(work);
tx.commit();
session.close();
5.使用元数据
Hibernate需要通过对象-关系映射文件中的元数据,来了解域模型中的持久化类及其属性的类型。
Hibernate为此提供了ClassMetadata和CollectionMetadata接口,让应用程序能够访问元数据,SessionFactory的getClassMetadata()和getCollectionMetadate()方法分别返回这两个接口的实例。
6.调用存储过程
在Oracle数据库中定义一个名为batchUpdateCustomer()的存储过程:
create or replace procedure batchUpdateCustomer(p_age in number) as
begin
update CUSTOMERS set AGE=AGE+1 where AGE>0;
end;
应用程序调用该存储过程:
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Work work = new Work(){
public void execute(Connection connection) throws SQLException{
String procedure = "{call batchUpdateCustomer(?)}";
CallableStatement cstmt = connection.prepareCall(procedure);
stmt.setInt(1,0).executeUpdate();
}
};
session.doWork(work);
tx.commit();
session.close();