Ibatis之RowHandler

如果一个场景:账户表中有1千万账户,现在,我们需要这个1千万账户利息结算业务。需求是基于Ibatis框架来实现这个功能。

如果按照一般的编程模式,我们将编写一个sql,然后调用QueryForList获得帐户List。然后遍历List逐条对数据进行结息操作,但这样做非常可能会出现性能问题,如:

1.对JVM内存的大量消耗;

2.大量对象的密集创建和销毁对GC带来非常大的负担;

出现性能问题的解决办法在于:QueryForList是把查询结果构造完毕以后才交给结息程序的,这意味着JVM会爆发性的创建一千万个对象到内存中,而且这1千万个对象会在内存中持续非常长时间——由于仅仅有等全部帐户结完息之后这些对象才会失去引用,那么能够判断这些对象也是爆发式的销毁。这对内存的消耗是非常巨大的。

为应对这种问题,Ibatis提供了RowHandler接口,同意程序猿对查询结果进行自己定义的处理,RowHandler接口代码例如以下:

/**
 * Event handler for row by row processing.
 * <p/>
 * The RowHandler interface is used by the SqlMapSession.queryWithRowHandler() method.
 * Generally a RowHandler implementation will perform some row-by-row processing logic
 * in cases where there are too many rows to efficiently load into memory.
 * <p/>
 * Example:
 * <pre>
 * sqlMap.queryWithRowHandler ("findAllEmployees", null, new MyRowHandler()));
 * </pre>
 */
public interface RowHandler {

  /**
   * Handles a single row of a result set.
   * <p/>
   * This method will be called for each row in a result set.  For each row the result map
   * will be applied to build the value object, which is then passed in as the valueObject
   * parameter.
   *
   * @param valueObject The object representing a single row from the query.
   * @see com.ibatis.sqlmap.client.SqlMapSession
   */
  void handleRow(Object valueObject);

}

DefaultRowHandler是RowHandler接口的默认实现,代码例如以下:

public class DefaultRowHandler implements RowHandler {

  private List list = new ArrayList();

  public void handleRow(Object valueObject) {
    list.add(valueObject);
  }

  public List getList() {
    return list;
  }

  public void setList(List list) {
    this.list = list;
  }

}

我们调用queryForList方法得到的list便是由该接口提供的。Ibatis构造这个list的过程大致例如以下:当运行完查询sql得到ResultSet之后。会循环遍历这个ResultSet,在每一次循环中调用DefaultRowHandler的handleRow方法,循环结束list也就构造完毕了,然后把list返回。到这里相信大家已经知道RowHandler的本质了,我们仅仅须要定义自己的Handler,然后调用queryWithRowHandler方法,Ibatis运行的就是我们自己定义的handleRow方法了。在handleRow方法里把结息操作做了,就不会出现内存暴增的情况了。由于每次循环结束valueObject便失去了句柄引用。非常快就会被GC清理掉了。

上面通过自己定义RowHandler攻克了内存占用的问题,但还有其它问题,handleRow中的结息操作是同步运行还是异步运行呢?假设是同步运行。那可能会带来新的问题:

首先,同步运行并没有降低总时间,用queryForList和用queryWithRowHander的总时间都为——(数据库端的查询时间+Java端取数据的时间+10000000*结息时间)。

再有,ResultSet并非一次性把1千万条数据返回的,而是在循环遍历的过程中随用随取,比方先取10条,运行到第11次循环的时候再取10条,依此类推,而一次取多少和FetchSize的设置有关系,那么在1千万个账户结息完毕之前数据库连接都无法释放而且数据库端会维持一个Cursor配合ResultSet取数据。这会带来长时间的、非常大的资源开销。

那么。将结息息操作异步化则是更好的方案。我们能够在handlRow方法中把账户对象Add到还有一个或多个线程的结息队列中,那么取数据和结息便能并行运行,这样总时间会少一些(FetchSize不变的情况下缩短的是结息的时间。数据库端的查询时间和Java端取数据的时间是不变的)。但可能不会少太多。这和FethSeize的设置有关系。假设FetchSize比較大,[Java端取数据的时间]会非常短,那并行的时间也会非常短。则短时间内完毕结息的账户数量也不会太多;假设FetchSize比較小,那取数据的时间会较长。并行的时间也会长,则完毕结息的账户数量也会多一些。

大家应该已经看到了,事实上异步化之后。假设FetchSize非常大的话,queryWithRowHandler和queryForList就没什么差别了,由于数据非常快就能取完(我做測试:两千六百多万条记录3分多钟就取完了)。相同会带来内存的暴增。而FetchSize太小的话又会导致Java和数据库之间频繁的网络IO,一样影响性能。所以FetchSize的设置就非常关键,太大太小都不行,须要考虑[取数据]的速率、[结息操作]的速率和内存大小三方面的要素。

上面说了这么多,都是偏理论性的东西,一切都需依据自己的实际情况进行抉择。

* 假设内存够大,queryForList一样非常OK;

* 假设内存是瓶颈,能够改用queryWithRowHandler,假设连接池不紧张。数据库端没什么资源压力,同步handle一样OK。

* 假设还有优化的需求,则能够考虑异步handle,但是这一次FetchSize设置需要仔细权衡,这不是太大,也不能太小。

版权声明:本文博主原创文章,博客,未经同意不得转载。

时间: 2024-10-16 19:13:59

Ibatis之RowHandler的相关文章

Mybatis/Ibatis,数据库操作的返回值

该问题,我百度了下,根本没发现什么有价值的文章:还是看源代码(详见最后附录)中的注释,最有效了!insert,返回值是:新插入行的主键(primary key):需要包含<selectKey>语句,才会返回主键,否则返回值为null.update/delete,返回值是:更新或删除的行数:无需指明resultClass:但如果有约束异常而删除失败,只能去捕捉异常.queryForObject,返回的是:一个实例对象或null:需要包含<select>语句,并且指明resultMap

iBatis系列一

XML iBatis可以使用xml来作为参数输入以及结果返回:这个功能的优势在于某些特定的场景:还有可以通过DOM方式来作为参数传递:但是这个方式应用的比较少,如果服务器是xml服务器可以采用这种方式: LazeLoad与groupby技术 对于海量数据,如果一次性就加载可能比较麻烦,这里就需要一些技术来对返回信息进行控制,LazyLoad技术大家都比较清楚,只有需要的时候再从DB中取值:iBatis里面支持LazyLoad,但是LazyLoad的设置是全局性质的,groupby的实现则实现了针

ibatis框架的sqlmapclient接口

SqlMapClient,是iBatis中的重要接口,这个接口涉及到对SQL映射的执行和批处理. 现在,就先了解它的对query开头方法的定义. 首先是 queryForList 方法: //指定SQL的ID,执行的结果返回ListqueryForList(java.lang.String id) ; //指定SQL的ID,并指定返回的行数queryForList(java.lang.String id, int skip, int max) ; //指定SQL的ID,并指定传入参数queryF

【转】 Mybatis/Ibatis,数据库操作的返回值

该问题,我百度了下,根本没发现什么有价值的文章:还是看源代码(详见最后附录)中的注释,最有效了!insert,返回值是:新插入行的主键(primary key):需要包含<selectKey>语句,才会返回主键,否则返回值为null.update/delete,返回值是:更新或删除的行数:无需指明resultClass:但如果有约束异常而删除失败,只能去捕捉异常.queryForObject,返回的是:一个实例对象或null:需要包含<select>语句,并且指明resultMap

【转】Mybatis/Ibatis,数据库操作的返回值

该问题,我百度了下,根本没发现什么有价值的文章:还是看源代码(详见最后附录)中的注释,最有效了!insert,返回值是:新插入行的主键(primary key):需要包含<selectKey>语句,才会返回主键,否则返回值为null.update/delete,返回值是:更新或删除的行数:无需指明resultClass:但如果有约束异常而删除失败,只能去捕捉异常.queryForObject,返回的是:一个实例对象或null:需要包含<select>语句,并且指明resultMap

ibatis代码生成器

1.ibatis代码生成器 首先cmd定位到ibator.jar文件,注意ibatorConfig.xml文件需要与之在同一文件夹中 命令:java -jar ibator.jar -configfile ibatorConfig.xml 2.ibatorConfig.xml文件 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ibatorConfiguration PUBLIC "-/

通过maven test 报org.apache.ibatis.binding.BindingException: Invalid bound statement

背景 直接使用eclipse工具去执行,没有问题,通过testng.xml去执行,没有问题,但通过mvn clean test执行,就报错,提示org.apache.ibatis.binding.BindingException: Invalid bound statement 解决方法 首先先肯定的是:mybatis的配置是没有问题,因为eclipse可以正常执行: 在eclipse中把mapper的xml文件放到src代码目录下是可以一起打包进classes的,而maven去编译的时候不会,

hibernate和ibatis对比

Hibernate是当前最流行的O/R mapping框架,iBATIS是另外一种优秀的O/R mapping框架. Hibernate对数据库结构提供了较为完整的封装,Hibernate的O/R Mapping实现了POJO和数据库表之间的映射,以及SQL的自动生成和执行.程序员往往只需定义好了POJO到数据库表的映射关系,即可通过Hibernate提供的方法完成持久层操作.Hibernate/OJB会根据制定的存储逻辑,自动生成对应的SQL并调用JDBC接口加以执行. 而iBATIS的着力点

iBatis系列之三

iBatis和Hibernate最大差别就是在于iBatis没有严格的和具体的表做关联:而是将结果集和DAO做关联. iBatis的SqlConfig.xml配置一个properties文件,其实可以完全在SqlConfig中配置相应的数据库信息,但是有了一个properties文件之后有一个好处:如果是从生产环境切换到测试环境,只需要修改一处"properties"节点即可. DAO模式就是通过生命一个DAO的接口形式来隐藏数据库层具体的实现:DAO模式提供的规则(接口的方法以及方法