测试环境:
windows8
MySQL5.6
Hibernate3
Hibernate批量插入
使用 Hibernate 将 一百万条记录插入到数据库的一个很天真的,错误的做法可能是这样的:
错误代码
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<1000000; i++ ) {
MotherCat m = new MotherCat("明子","黑色");
session.save(m);
}
tx.commit();
session.close();
这段程序要么运行一段时间会失败并抛出内存溢出异常(OutOfMemoryException) ,要么执行时间太长,导致效率低下。
这是因为 Hibernate 把所有新插入的MotherCat实例在 session 级别的缓存区进行了缓存的缘故。
解决:
首先,如果你要执行批量处理并且想要达到一个理想的性能,那么使用 JDBC 的批量处理功能是至关重要。JDBC 的批量抓取数量(batch size)参数设置到一个合适值:
<property name="hibernate.jdbc.batch_size">50</property>
注意,假若你使用了 identiy 标识符生成器,Hibernate会在 JDBC 级别透明的关闭插入语句的批量执行。
也可以在执行批量处理时完全关闭二级缓存:
<property name="hibernate.cache.use_second_level_cache">false</property>
但是,这不是绝对和必须的,因为我们可以显式设置 CacheMode 来关闭与二级缓存的交互。
正确代码:
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<1000000; i++ ) {
MotherCat m = new MotherCat("明子","黑色");
session.save(m);
if ( i % 50 == 0 ) { //50, 和配置的JDBC batch size一样
//刷出并同步到底层持久化, 批量的插入并且释放内存:
session.flush();
//完全清除session
session.clear();
}
}
tx.commit();
session.close();
以上代码虽然不会出现内存溢出,但效率不怎么好。
优化代码
使用StatelessSession(无状态 session)接口
接口功能:
- 用托管对象的形式把数据以流的方法加入到数据库,或从数据库输出。
- 没有持久化上下文,也不提供多少高层的生命周期语义。
- 不实现和第一级cache(缓存),也不和第二级缓存,或者查询缓存交互。
- 不实现事务化写,也不实现脏数据检查。
- 不级联到关联实例。
- 忽略集合类(Collections)。
- 不触发 Hibernate 的事件模型和拦截器。
- 对数据的混淆现象免疫,因为它没有第一级缓存。
- 是低层的抽象,和低层 JDBC 相当接近。
以下给出优化代码:
StatelessSession session = HibernateUtil.getSessionFactory().openStatelessSession(); //打开无状态的session
Transaction tx = session.beginTransaction();
for ( int i=0; i<1000000; i++ ) {
MotherCat m = new MotherCat();
m.setMname("大米");
m.setColor("黑色");
session.insert(m);
}
tx.commit();
session.close();
StatelessSession 接口定义的 insert(), update() 和 delete() 操作是直接的数据库行级别操作,其结果是立刻执行一条 INSERT, UPDATE 或 DELETE 语句。
因此,它们的语义和 Session 接口定义的 save(), saveOrUpdate() 和delete() 操作有很大的不同。
PS:更进一步的优化是使用原生SQL语句和StatelessSession接口结合的方式。可能还有其他优化方式,欢迎大家指教。
以上。。。