在项目的开发过程之中,我们常会遇到数据的批量处理问题。在持久层采用Hibernate框架时,在进行批量操作时,需要考虑Hibernate实现机制带来的一些问题。

我们知道在每个Hibernate Session中都维持了一个必选的数据缓存,所有保存的实例都将保存在Session缓存中,这个缓存随着Session的创建而存在,随着Session的销毁而消亡。这个内部缓存正常情况下是由Hibernate自动维护的,并且没有容量限制。在批量插入与更新时,由于每次保存的实体都会保存在Session缓存中,当数据量大的时候,就可能出现OutOfMemoryException(内存溢出异常)。所以批量增加或更新操作中, 应该考虑到控制内部缓存的过度增长而出现OutOfMemeoryError错误。

这里有两个方面进行控制:一是在数据保存过程中周期性的对Session调用flush和clear方法,确保Session的容量不至于太大。二是设置hibernate.jdbc.batch_size参数来指定每次提交SQL的数量。

周期性调用session的flush和clear方法,最简单的一个方式是:设置一个计数器,每保存一个实例时,计数器加1。根据计数器的值决定是否需要将Session缓存中的数据刷入数据库。

配置hibernate.jdbc.batch_size参数的原因就是尽量少读数据库,hibernate.jdbc.batch_size参数值越大,读数据库的次数越少,速度越快。

另外一个需要考虑的问题是在批量更新或批量删除时,最简单的思路是都是先将符合要求的数据查出来,然后再做更新/删除操作。而且更新/删除操作通常是逐行更新/删除,即每更新/删除一行记录,都需要执行一条update/delete语句。这样做的目的在于缓存同步,所以往往是一次批量更新/删除往往执行的是1+N次操作. 第一次是查询, 第二次是逐条更除/删除。由于带来的问题是内存消耗。特别是过大数据量时还可能在查询时导致OutOfMemeoryError错误。

这里可以考虑使用session.iterator或者Query.iterate方法逐条获取数据,或者采用基于游标的数据遍历操作(JDBC驱动需支持游标), 通过游标来逐条获取数据,从而控制内存的使用。(注:iterate方法首先从本地缓存中根据id查找对应的实体对象——类似Session.load方法,如果实体在缓存中已经存在,则直接以此数据对象作为查询结果,如果没找到,再执行相应的Select语句获得对应的库表记录)

另外也可以采用Hibernate 3.0后提供的批量更新/删除接口。Hibernate3.0 采用新的基于ANTLR的HQL/SQL查询翻译器,在Hibernate的配置文件中hibernate.query.factory_class属性用来选择查询翻译器。

1)选择Hibernate3.0的查询翻译器:hibernate.query.factory_class= org.hibernate.hql.ast.ASTQueryTranslatorFactory

2)选择Hibernate2.1的查询翻译器:hibernate.query.factory_class= org.hibernate.hql.classic.ClassicQueryTranslatorFactory

为了使用3.0的批量更新和删除功能,只能选择ASTQueryTranslatorFactory,否则不能解释批量更新的语句。

注:ANTLR是用纯Java语言编写出来的一个编译工具,它可生成Java语言或者是C++的词法和语法分析器,并可产生语法分析树并对该树进行遍历。ANTLR由于是纯Java的,因此可以安装在任意平台上,但是需要JDK的支持。

下面就分别从批量插入、批量更新和批量删除3个方面总结一下Hibernate批量处理的情开。

批量增加

由于批量增加会带来session级缓存的增长,所以我们一般在数据保存过程中周期性地调用Session的flush和clear方法。如下:

public <T> int batchSave(final T[] array) {

Transaction tx = session.beginTransaction();

for(int i = 0; i < array.length; i++) {

session.save(array[i]);

if (i % BATCH_MAX_ROW == 0) {

session.flush();

session.clear();

}

}

session.flush();

session.clear();

tx.commit();

return array.length;

}

上面代码中,当i % BATCH_MAX_ROW == 0时,就手动Session处的缓存数据写入数据库。

其中,Session.flush()方法会完成两个主要任务(见DefaultFlushEventListener类):1.刷新所有数据;2.执行数据库SQL完成持久化动作;flush方法必须在操作结束且在提交事务和关闭连接之前被调用。Session.clear()则是清除session中的缓存数据。这样就达到控制session的一级缓存的大小。

如果在Spring+Hibernate环境下,利用Spring提供的Hibernate模板,我们可以如下定义:

public <T> int batchSave(final T[] array) {

int affectedRow = (Integer) getHibernateTemplate().execute(

new HibernateCallback() {

public Object doInHibernate(Session session)

throws HibernateException, SQLException {

for (int i = 0; i < array.length; ++i) {

session.save(array[i]);

if (i % BATCH_MAX_ROW == 0) {

session.flush();

session.clear();

}

}

session.flush();

session.clear();

return array.length;

}

});

return affectedRow;

}

我们也可以通过SQL的方式来批量增加数据,那么在JDBC中使用批量增加的情况如下:

Statement stmt = connection.createStatement();

connection.setAutoCommit(false);//将Auto commit设置为false,不允许自动提交

stmt.addBatch("insert into employee values(23,'wang','man',20)");

stmt.addBatch("insert into employee values(24,'xiaowu','woman',24)");

stmt.executeBatch();   //将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组

connection.setAutoCommit(true);

以上代码显示的是利用JDBC Statement接口来处理批量增加的情况,其中,statement接口中的两个方法:

addBatch(String sql)——在批处理缓存中加入一条sql语句;

executeBatch()——执行批处理缓存中的所有sql语句

另外一种方式是利用PrepareStatement来处理批量增加,如下:

PreparedStatement  pstm = connection.prepareStatement("insert into employee values(?,?,?,?)");

connection.setAutoCommit(false);//将Auto commit设置为false,不允许自动提交

//设置第一条语句

pstm.setInt(1, 33);

pstm.setString(2,"wang");

pstm.setString(3, "man");

pstm.setDouble(4, 20);

pstm.addBatch();  //将一组参数添加到此 PreparedStatement 对象的批处理命令中。

//设置第二条语句

pstm.setInt(1, 34);

pstm.setString(2,"xiaowu");

pstm.setString(3, "woman");

pstm.setDouble(4, 24);

pstm.addBatch();

pstm.executeBatch();//将一批参数提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组

connection.commit();

connection.setAutoCommit(true);//将Auto commit还原为true

其中, PreparedStatement接口中的两个方法:

addBatch()——将一组参数添加到PreparedStatement对象中。

executeBatch()——将一批参数提交给数据库来执行,如果命令执行成功,则返回更新计数组成的数组。

在以上的代码中,只需使用Hibernate连接来改造上述代码就可实现Hibernate利用JDBC来批量增加。

批量修改

批量增加的思路同样适用于批量更新数据,如果需要返回多行数据,可以使用scroll()方法,从而可充分利用服务器端游标所带来的性能优势。如下代码:为

String hqlString = ...;

Transaction tx = session.beginTransaction();

Iterator iter = session.find(hqlString).iterator();

//ScrollableResults users = session.createQuery(hqlString)

//    .setCacheMode(CacheMode.IGNORE)

//    .scroll(ScrollMode.FORWARD_ONLY);

int count=0;

while(iter.hasNext()){

Oject obj = iter.next();

// do something here….

if (++count % BATCH_MAX_ROW == 0 ) {

session.flush();

session.clear();

}

}

tx.commit();

session.close();

如前所述,我们应该力求避免出现先执行数据查询进行数据逐行更新(即每更新一行记录,都需要执行一条update语句)的情况。实际上,我们可考虑利用Hibernate提供的类似于SQL的批量更新/删除的HQL语法来进行批量操作。

我们先看一下在SQL中进行批量更新的操作:

采用Statement接口进行更新:

Statement stmt = connection.createStatement();

String sqlString = "update s set age=20 where id=s1";

stmt.executeUpdate(sql);

采用PreparedStatement接口进行更新:

PreparedStatement stmt = connection.prepareStatement("update s set age=? where id=?");

stmt.setObject(1, "20");

stmt.setObject(2, "s1");

int i = statement.executeUpdate();

我们在Hibernate中绕过Hibernate API,通过JDBC API来执行SQL语句,示例:

String sqlString = ...;  //定义批量更新的SQL语句

tx = session.beginTransaction();

Connection con = session.connection();

PreparedStatement stmt = con.prepareStatement(sqlString);

//set the parameter

stmt.executeUpdate();

tx.commit();

利用Spring模板示例:

public Integer executeBySql(final String sqlString, final Object[] values)

throws HibernateException, SQLException {

return (Integer) getHibernateTemplate().execute(

new HibernateCallback() {

public Object doInHibernate(Session session) throws HibernateException, SQLException {

Transaction tx = session.beginTransaction();

Integer result = -1;

try {

tx.begin();

SQLQuery query = session.createSQLQuery(sqlString);

for (int k = 0; k < values.length; k++) {

query.setParameter(k, values[k]);    //按位置进行绑定。。

}

result = query.executeUpdate();

tx.commit();

} catch (HibernateException e) {

e.printStackTrace();

if (tx != null) {

tx.rollback();

}

} finally {

session.clear();

}

return result;

}

}

);

}

利用Hiberante3提供的批量操作接口处理如下:

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

String hqlUpdate = "update Customer set name = :newName where name = :oldName";

int updatedEntities = s.createQuery( hqlUpdate )

.setString( "newName", newName )

.setString( "oldName", oldName )

.executeUpdate();

tx.commit();

session.close();

由Query.executeUpdate()方法返回一个整型值,该值是受此操作影响的记录数量。实际上,Hibernate的底层操作是通过JDBC完成的。因此,如果有批量的UPDATE或DELETE操作被转换成多条UPDATE或DELETE语句,该方法返回的是最后一条SQL语句影响的记录行数。

利用Spring模板示例:

public Integer exeByHQL(final String hqlString, final Object[] params) {

return (Integer) getHibernateTemplate().execute(

new HibernateCallback() {

public Object doInHibernate(Session session) throws HibernateException, SQLException {

Integer result = -1;

Transaction tx = session.beginTransaction();

Query query = session.createQuery(hqlString);

for (int i = 0; i < params.length; i++)

query.setParameter(i, params[i]);

result = Integer.valueOf(query.executeUpdate());

tx.commit();

return result;

}

});

}

如果底层数据库支持存储过程,也可以通过存储过程来执行批量更新。以下是利用Spring模板的一个例子:

public String executeByProcedure(final String sqlString, final Object[] values)

throws HibernateException, SQLException {

return (String) getHibernateTemplate().execute(

new HibernateCallback() {

public Object doInHibernate(Session session) throws HibernateException, SQLException {

try {

Connection connection = session.connection();

CallableStatement cstm = connection.prepareCall(sqlString);

String as[] = values;

int j = as.length;

for (int k = 0; k < j; k++) {

String s1 = as[k];

cstm.setString(k, s1);

}

cstm.executeUpdate();

String s = cstm.getString(1);

if (cstm != null)

cstm.close();

return s;

}

catch (RuntimeException runtimeException) {

runtimeException.printStackTrace();

throw runtimeException;

}

}

}

);

}

在Hiberante中的示例如下;

Transaction tx = session.beginTransaction();
Connection con=session.connection();
String procedure = "{call batchUpdateXXX(?) }";
CallableStatement cstmt = con.prepareCall(procedure);
cstmt.setInt(1,0); 
cstmt.executeUpdate();
tx.commit();

批量删除

批量删除与批量修改类似,以下是通过Hibernate3.0执行批量删除的程序代码:

代码

Session session = sessionFactory.openSession();

Transaction tx = session.beginTransaction();

String hqlDelete = "delete from Customer where name = :oldName";

int deletedEntities = s.createQuery( hqlDelete )

.setString( "oldName", oldName )

.executeUpdate();

tx.commit();

session.close();

通过JDBC API执行相关的SQL语句或调用相关的存储过程是批量更新和批量删除的理想方式,它具有以下优点:

(1) 不会消耗大量内存。因为它不再将数据库中的大批量数据先加载到内存中,然后再逐个更新或修改。

(2) 可以在一条SQL语句中更新或删除大批量的数据。

无论是直接引用SQL方式还是使用Hibernate 3的bulk接口, 通过一条的SQL完成对数据的批量删除/更新都在一个问题:无法解决缓存同步问题(包括一级缓存和二级缓存)。为了保持缓存数据的一致性,简单的办法就是清空缓存数据。

Hibernate批量操作(一)的更多相关文章

  1. Hibernate批量操作(二)

    Hibernate提供了一系列的查询接口,这些接口在实现上又有所不同.这里对Hibernate中的查询接口进行一个小结. 我们首先来看一下session加载实体对象的过程:Session在调用数据库查 ...

  2. 不要依赖hibernate的二级缓存

    一.hibernate的二级缓存   如果开启了二级缓存,hibernate在执行任何一次查询的之后,都会把得到的结果集放到缓存中,缓存结构可以看作是一个hash table,key是数据库记录的id ...

  3. Hibernate的一些使用技巧

    1.Hibernate是如今最流行的开源对象关系映射(ORM)持久化框架,SSH框架组合是很多JavaEE工程的首选,java持久化框架(JPA)的设计师是Hibernate的作者,因此对于Hiber ...

  4. 【Hibernate框架】批量操作Batch总结

    在我们做.net系统的时候,所做的最常见的批量操作就是批量导入.插入.更新.删除等等,以前我们怎么做呢?基本上有以下几种方式: 1.利用循环调用insert方法,一条条插入. public boole ...

  5. Hibernate管理Session和批量操作

    Hibernate管理Session Hibernate自身提供了三种管理Session对象的方法 Session对象的生命周期与本地线程绑定 Session对象的生命周期与JTA事务绑定 Hiber ...

  6. Hibernate深入浅出(九)持久层操作——数据保存&批量操作

      数据保存: 1)session.save session.save方法用于实体对象到数据库的持久化操作.也就是说,session.save方法调用与实体对象所匹配的Insert SQL,将数据插入 ...

  7. Hibernate的批量操作

    在实际的操作中,会经常的遇到批量的操作,使用hibernate将 100条记录插入到数据库的一个很自然的做法可能是这样的 Session session = sessionFactory.openSe ...

  8. 《使用Hibernate开发租房系统》内部测试笔试题

    笔试总结 1.在Hibernate中,以下关于主键生成器说法错误的是( C). A.increment可以用于类型为long.short或byte的主键 B.identity用于如SQL Server ...

  9. hibernate.cfg.xml常见配置

    转载自:http://blog.csdn.net/qiaqia609/article/details/9456489 <!--标准的XML文件的起始行,version='1.0'表明XML的版本 ...

随机推荐

  1. CoolBlog开发笔记第3课:创建Django应用

    教程目录 1.1 CoolBlog开发笔记第1课:项目分析 1.2 CoolBlog开发笔记第2课:搭建开发环境 前言 经过上一节我们已经创建了CoolBlog工程,但是关于CoolBlog的功能代码 ...

  2. DataTable多线程操作报错情况

    最近在写一个http接口时用了DataTable这个强大的利器,接口用浏览器跑起来没任何问题.当时也没考虑并发问题,后来用一个压力测试工具做大并发测试,1000+/s次速度测试.发现程序报错了.程序报 ...

  3. 一些爬虫中的snippet

    1.tornado 一个精简的异步爬虫(来自tornado的demo) #!/usr/bin/env python import time from datetime import timedelta ...

  4. 【LeetCode】258. Add Digits

    题目: Given a non-negative integer num, repeatedly add all its digits until the result has only one di ...

  5. 使用 Live555 搭建流媒体服务器

    最近因为工作需要,需要搭建流媒体服务器,所以研究了一下,在此分享我的搭建过程. 搭建过程还是非常简单的! 搭建环境为Centos 7.2 64bit 一.安装gcc编译器 yum install gc ...

  6. Linux离线安装Ruby详解

    很多时候我们会发现,真实的生成环境很多都没有外网,只有内网环境,这个时候我们又需要安装Ruby,则不能提供yum命令进行在线安装了,这个时候我们就需要下载安装包进行离线安装.本文主要简单介绍如果离线安 ...

  7. English - Green Peanut Butter

    There is a guy. He wants to drink 12 cups of green peanut butter. He needs green peanut butter. So h ...

  8. 同一个tomcat多个项目共享session,一个tomcat两个项目共享sessionId

    同一个tomcat多个项目共享session,一个tomcat两个项目共享sessionId >>>>>>>>>>>>>& ...

  9. C#.NET 中visual studio生成的.pdb/ .vshost.exe/ .vshost.exe.manifest文件是什么

      pdb文件: 英文全称:Program Database File 中文全称:程序数据库 文件 Debug里的PDB是full,保存着调试和项目状态信息.有断言.堆栈检查等代码.可以对程序的调试配 ...

  10. jvm003 类加载的过程

    类加载的过程 一.加载 在加载阶段虚拟机需要完成以下三件事: 通过一个类的全限定名称来获取此类的二进制字节流 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构 在内存中生成一个代表这个类的 ...