问题:在Spring下怎么使用iBatis的批处理实现? 

从4个层面分析这部分实现: 

  1. iBatis的基本实现

  2. 基于事务的iBatis的基本实现

  3. 基于事务的Spring+iBatis实现

  4. 基于回调方式的Spring+iBatis实现



1.iBatis的基本实现 
iBatis通过SqlMapClient提供了一组方法用于批处理实现: 

  1. startBatch() 开始批处理

  2. executeBatch() 执行批处理


代码如下: 

Java代码 

  1. public void create(List<Reply> replyList) {  

  2.   

  3.     try {  

  4.         // 开始批处理  

  5.         sqlMapClient.startBatch();  

  6.   

  7.         for (Reply reply: replyList) {  

  8.             // 插入操作  

  9.             sqlMapClient.insert("Reply.create", reply);  

  10.         }  

  11.         // 执行批处理  

  12.         sqlMapClient.executeBatch();  

  13.   

  14.     } catch (Exception e) {  

  15.         e.printStackTrace();  

  16.     }  

  17. }  


这是基于iBatis的最基本实现,如果你一步一步debug,你会发现:其实,数据库已经执行了插入操作! 
因此,除了这两个核心方法外,你还需要开启事务支持。否则,上述代码只不过是个空架子! 

2.基于事务的iBatis的基本实现 
事务处理: 

  1. startTransaction() 开始事务

  2. commitTransaction() 提交事务

  3. endTransaction() 结束事务



我们以insert操作为例,把它们结合到一起: 

Java代码 

  1. public void create(List<Reply> replyList) {  

  2.   

  3.     try {  

  4.         // 开始事务  

  5.         sqlMapClient.startTransaction();  

  6.         // 开始批处理  

  7.         sqlMapClient.startBatch();  

  8.   

  9.         for (Reply reply: replyList) {  

  10.             // 插入操作  

  11.             sqlMapClient.insert("Reply.create", reply);  

  12.         }  

  13.         // 执行批处理  

  14.         sqlMapClient.executeBatch();  

  15.   

  16.         // 提交事务  

  17.         sqlMapClient.commitTransaction();  

  18.   

  19.     } catch (Exception e) {  

  20.         e.printStackTrace();  

  21.     } finally {    

  22.              try {  

  23.             // 结束事务  

  24.             sqlMapClient.endTransaction();  

  25.                 } catch (SQLException e) {  

  26.                          e.printStackTrace();  

  27.                      }  

  28.     }    

  29. }  


replyList是一个List,要把这个List插入到数据库,就需要经过这三个步骤: 

  1. 开始批处理 startBatch()

  2. 插入      insert()

  3. 执行批处理 executeBatch()


如果要在Spring+iBatis中进行批处理实现,需要注意使用同一个sqlMapClient!同时,将提交事务的工作交给Spring统一处理! 

3.基于事务的Spring+iBatis实现 

Java代码 

  1. public void create(List<Reply> replyList) {  

  2.     if (!CollectionUtils.isEmpty(replyList)) {  

  3.         // 注意使用同一个SqlMapClient会话  

  4.         SqlMapClient sqlMapClient = sqlMapClientTemplate.getSqlMapClient();  

  5.   

  6.         try {  

  7.             // 开始事务  

  8.             sqlMapClient.startTransaction();  

  9.             // 开始批处理  

  10.             sqlMapClient.startBatch();  

  11.             for (Reply reply : replyList) {  

  12.                 // 插入操作  

  13.                 sqlMapClient.insert("Reply.create", reply);  

  14.             }  

  15.   

  16.             // 执行批处理  

  17.             sqlMapClient.executeBatch();  

  18.             // 提交事务 交给Spring统一控制  

  19.             // sqlMapClient.commitTransaction();  

  20.   

  21.         } catch (Exception e) {  

  22.             e.printStackTrace();  

  23.         } finally {    

  24.                  try {  

  25.                 // 结束事务  

  26.                 sqlMapClient.endTransaction();  

  27.                     } catch (SQLException e) {  

  28.                              e.printStackTrace();  

  29.                          }  

  30.         }    

  31.     }  

  32. }  


注意使用同一个sqlMapClient: 
SqlMapClient sqlMapClient = sqlMapClientTemplate.getSqlMapClient(); 
如果直接sqlMapClientTemplate执行insert()方法,将会造成异常! 

想想,还有什么问题?其实问题很明显,虽然解决了批处理实现的问题,却造成了事务代码入侵的新问题。Ibatis中进行批量操作_create 这么做,有点恶心! 
除此之外,异常的处理也很恶心,不能够简单的包装为 DataAccessException 就无法被Spring当作统一的数据库操作异常做处理。 


4.基于回调方式的Spring+iBatis实现 
如果观察过Spring的源代码,你一定知道,Spring为了保持事务统一控制,在实现ORM框架时通常都采用了回调模式,从而避免了事务代码入侵的可能!Ibatis中进行批量操作_create_02 
修改后的代码如下: 

Java代码 

  1. @SuppressWarnings("unchecked")  

  2. public void create(final List<Reply> replyList) {  

  3.     // 执行回调  

  4.     sqlMapClientTemplate.execute(new SqlMapClientCallback() {  

  5.         // 实现回调接口  

  6.         public Object doInSqlMapClient(SqlMapExecutor executor)  

  7.                 throws SQLException {  

  8.             // 开始批处理  

  9.             executor.startBatch();  

  10.             for (Reply reply : replyList) {  

  11.                 // 插入操作  

  12.                 executor.insert("Reply.create", reply);  

  13.   

  14.             }  

  15.             // 执行批处理  

  16.             executor.executeBatch();  

  17.   

  18.             return null;  

  19.   

  20.         }  

  21.     });  

  22.   

  23. }  


注意,待遍历的参数replyList需要加入final标识!即,待遍历对象不能修改! 

引用

public void create(final List<Reply> replyList)


这样做,就将事务处理的控制权完全交给了Spring!Ibatis中进行批量操作_create_02 
简述: 

  1. SqlMapClientCallback 回调接口

  2. doInSqlMapClient(SqlMapExecutor executor) 回调实现方法

  3. DataAccessException 最终可能抛出的异常

http://michael-softtech.iteye.com/blog/620433

 

     最近遇到这样一个客户需求:需要向数据库里面一次插入几万条数据。系统的Persistence层用的是ibatis,

事务是通过spring管理。之前都是少量数据的操作,所以都是按照以下方式插入的:

 

 

Java代码   Ibatis中进行批量操作_create_04

  1. class Service extends SqlMapClientDaoSupport  

  2. {  

  3.  public void insert(...)  

  4. {  

  5.   getSqlMapClientTemplate().insert(..);  

  6.     

  7. }  

  8.   

  9. }  

 

   但是数据量大时,速度奇慢。于是找时间读了一下ibatis的源码,终于发现了问题所在,记录如下:

 

   首先,过跟踪代码,发现sql的相关操作都有这样一个入口:

 

Java代码   Ibatis中进行批量操作_create_04

  1. public Object execute(SqlMapClientCallback action){ action.doInSqlMapClient(session);....}  

 

      上面的session是SqlMapSession的一个实例,而SqlMapSession继承了SqlMapExecutor接口,实际上以上的代码最终还是通过SqlMapExecutor的对应方法来实现(比如:session.insert(..)).

      于是继续追踪SqlMapSession的实现类:SqlMapSessionImpl。发现这个类的所有JDBC操作都是通过代理类SqlMapExecutorDelegate来实现的(这个代理类比SqlExecutor多了事务管理的配置:有一个TransactionManager)。这个代理类在每个单独的操作时,都先有这样一条语句:

 

 

Java代码   Ibatis中进行批量操作_create_04

  1.  trans = getTransaction(session);  

  2.  autoStart = trans == null;  

Java代码   Ibatis中进行批量操作_create_04

  1. trans = autoStartTransaction(session, autoStart, trans);  

 

    上述代码通过判断sutoStart来决定是不是开启一个事务。而autoStart是通过判断当前是不是已经有打开的事务

     来赋值的。那么就可以理解了:如果当前操作没有在事务下面,那么自动开启(取出)一个事务;如果已经有了事务,那么  直接使用当前事务。如果要进行批量操作,那么就必须在调用之前开启一个事务。所以就简单了:

 

Java代码   Ibatis中进行批量操作_create_04

  1. public Object operate(final List<CardInfo> cardsToAdd, final List<AcctInfo> acctsToAdd, final List<AcctInfo> acctsToUpdate) throws DataAccessException{  

  2.        

  3.   Object obj=this.getSqlMapClientTemplate().execute(new SqlMapClientCallback(){  

  4.      public Object doInSqlMapClient(SqlMapExecutor executor)  

  5.              {  

  6.        

  7.      try{  

  8.      getSqlMapClient().startTransaction();  

  9.      executor.startBatch();...  

   后面的startBatch语句是通过使用jdbc的批处理来提高效率。这样就能顺利执行同一个事务下的批量操作了(注意:如果在批量startBatch之前没有开启事务,批处理是无效的)。