您现在的位置是:首页-> 米鼠技术 ->iBatis框架batch处理优化(2)

iBatis框架batch处理优化(2)

 

 

 

 

 

iBatis框架做batch处理的问题

    在一个batch中只能对一个表进行操作,例如插入或删除。当有多个表需要处理时,只能放在多个batch中进行处理。看下面的一段代码:
 
    private void execute(int from,int to,List list){
        if(log.isDebugEnabled()){
            log.debug("STRGHousekeepTask execute start...");
        }
        HKSqlMapWrapper sqlWrapper = HKSqlMapWrapper.newInstance();
        sqlWrapper.startBatch();
        
        for(int i=from;i<to;i++){
            sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR_BL,list.get(i));
            sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR,list.get(i));
            sqlWrapper.delete(STRGHousekeepConstants.DELETE_CNTR,list.get(i));
        }
        sqlWrapper.execBatch();
        if(log.isDebugEnabled()){
            log.debug("STRGHousekeepTask execute end...");
        }
    }
                                            代码1

    这段代码的目的就是要删除数据库中3个表的数据,sqlWrapper是iBatis的SqlMapClient的一个包装器,主要是封状对事物的控制。当批次(既to-from的值)很小的时候,这样写是没有问题的。尽管这段代码的本意是要享受batch处理带来的好处,但是事实上这段代码并不会真正达到预期的效果,至于原因,我们一会在进行分析?。我们先来看下面一段代码:

    private void execute(int from,int to,List list){
        if(log.isDebugEnabled()){
            log.debug("STRGHousekeepTask execute start...");
        }
        HKSqlMapWrapper sqlWrapper = HKSqlMapWrapper.newInstance();
        sqlWrapper.startBatch();
        
        for(int i=from;i<to;i++){
            sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR_BL,list.get(i));
        }
        for(int i=from;i<to;i++){
            sqlWrapper.delete(STRGHousekeepConstants.DELETE_STRG_CNTR,list.get(i));
        }
        for(int i=from;i<to;i++){
            sqlWrapper.delete(STRGHousekeepConstants.DELETE_CNTR,list.get(i));
        }
        sqlWrapper.execBatch();
        if(log.isDebugEnabled()){
            log.debug("STRGHousekeepTask execute end...");
        }
    }

                                            代码2

    正如你所看到的,和代码1相比它只是做了3次循环,每个循环执行一个表的操作。虽然麻烦,但是却真正的享受到了batch处理的好处!下面是时候解释一下这两段代码幕后的秘密了?。
    在前面的章节里已经解释了JDBC如何做batch处理,如果还不清楚的话请查看前面的章节。要解释这两段代码里面的玄机,还得看一段代码?下面的代码是从iBatis源码中提取的:

    public void addBatch(RequestScope request, Connection conn, String sql, Object[] parameters  ) {
      PreparedStatement ps = null;
      if (currentSql != null
          && sql.hashCode() == currentSql.hashCode()
          && sql.length() == currentSql.length()) {
        int last = statementList.size() - 1;
        ps = (PreparedStatement) statementList.get(last);
      } else {
        ps = conn.prepareStatement(sql);
        currentSql = sql;
        statementList.add(ps);
      }
      request.getParameterMap().setParameters(request, ps, parameters);
      ps.addBatch();
      size++;
    }

    这就是iBatis中batch处理的做法,在这里不想对这段代码做一一解释,有兴趣的可以自己查看一下iBatis的源码,我们只关心它如何对一条语句进行处理。参数sql是要进行batch处理的语句,parameters是sql的参数列表,如果sql和实例变量currentSql相等,则从statementList列表里面得到一个PreparedStatement,然后进行batch处理,如果不等就新生成一个PreparedStatement对象,并把它加到statementList列表里面,并把当前sql的值附给currentSql,下次传递来sql的时候就会和这个新的currentSql比较。这就是为什么在一个循环里面只对一个表进行处理的原因了。如果在一个循环里面对多个表进行处理,每次传给addBatch方法的sql都是新的,都会生成一个新的PreparedStatement,所以也就享受不到batch处理带来的好处了!   

   按照代码1的方式执行程序,当batch size很小的时候尽管享受不到batch处理带来的好处,但是也不至于会出什么大问题,但是当batch size值很大的时候(我在程序中试验过1000-5000范围),数据库就会报错了!错误是"too many courses",原因是每生成一个PreparedStatement实例,就会相应的生成一个course。假设batch size是5000,要删除10个表的数据,那么产生的course的数目就是5000*10=50000,这对数据库来说是不能接受
的,所以就会报错。

    如果按照代码2的的方式写程序肯定是没有问题的,只会生成10个PreparedStatement实例,相应的也只会生成10个course,这样就真正的享受到了batch处理带来的好处。但是,作为一名“挑剔”的程序员,我们怎么能容忍这样的写法呢?明明一个循环就可以搞定,现在要分成10个循环来做,非但效率上存在问题,大量重复的代码也让我们的程序显得很没“水准”。

    既然第一种方式不能享受batch处理带来的好处,并且还会出错,第二种方式代码又非常的丑陋,那么我们就得想个办法来解决这个问题了。请记住:解决问题的过程就是一种享受?。

修改底层代码,支持多表batch处理

    既然出问题的地方找到了,那么解决它就很容易了。什么,你说还不知道问题出在哪?My God! Kill me ,pleale?!

    在这里分享一下我的思路,把每次传近来的sql作为key、把生成的PreparedStatement实例作为value放在一个Map里以后每次传来sql时先判断在Map里有没有这个key,如果有就直接拿到它的value作为PreparedStatement实例,如果没有就新生成一个PreparedStatement实例并把它放到Map里。这样有几个sql就有几个PreparedStatement
实例,和写多个循环效果是一样的。但写一个循环会更爽?!


热点文章
最新项目
相关文章 最新文章