在项目的开发过程之中,我们常会遇到数据的批量处理问题。在持久层采用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. 神奇的 conic-gradient 圆锥渐变

    感谢 LeaVerou 大神,让我们可以提前使用上这么美妙的属性. conic-gradient 是个什么?说到 conic-gradient ,就不得不提的它的另外两个兄弟: linear-grad ...

  2. JSONArray用法jquery循环list<Map>对象

    controoler中 List<Map<String,Object>> resList =(List<Map<String,Object>>)resM ...

  3. SVN仓库迁移到Git的完美解决办法

    参考文章Converting a Subversion repository to Git 1 使用git svn clone 拷贝svn仓库 cd ~/test_repo git svn clone ...

  4. springmvc常用注解标签详解【转】

    转载自:http://www.cnblogs.com/leskang/p/5445698.html 1.@Controller 在SpringMVC 中,控制器Controller 负责处理由Disp ...

  5. h5可预览 图片ajax上传 (补更),后台数据获取方法---php

    原理是 先获取,然后手动转移文件路径,不然会被服务器自动删除 demo如下: <?php header('content-Type:text/html;charset=utf-8'); $fil ...

  6. 将notepad++打造成java快速开发IDE

    参考文章:http://blog.csdn.net/mdyyzc/article/details/7653096 有时候要试验一小段代码,打开eclipse又需要忍受漫长的煎熬(电脑配置较低,见谅). ...

  7. CISCO2960配置vlan

    一.VTP配置 1.VLAN database 2.(VLAN)#vtp domain wx 3.(VLAN)#vtp server 二.VLAN配置 1.VLAN database 2.(VLAN) ...

  8. Spring MVC 基本注解

    1. Spring MVC 常用到的注解: @Controller @RequestMapping @RequestParam @RequestHeader @ModelAttribute @Path ...

  9. window10(64bit)+VS2010编译ACE_TAO源码库

    1.下载 ACE+TAO下载地址:http://download.dre.vanderbilt.edu/previous_versions/ VS2010下载地址:https://pan.baidu. ...

  10. cocos 射线检测 3D物体 (Sprite3D点击)

    看了很多朋友问怎么用一个3D物体做一个按钮,而且网上好像还真比较难找到答案, 今天翻了一下cocos源码发现Ray 已经封装了intersects函数,那么剩下的工作其实很简单了, 从屏幕的一个poi ...