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
实例,和写多个循环效果是一样的。但写一个循环会更爽?!
