iBatis批处理(batch)
本文重点:
1. 在执行批处理时,方法update和executeBatch的返回值(影响的记录数)不可靠。
2. 批处理必须在显式的事务中进行,并且关闭auto commit。
3. Batch大小。
一.JDBC批处理
和CRUD一样,iBatis通过JDBC支持,封装实现了自己的批处理。下面是一段使用JDBC进行批处理的代码:
Connection conn = ...; conn.setAutoCommit(false);
PreparedStatement ps = conn.prepareStatement("insert into tb (id) values (?)"); for (int i = 0; i < 1000; i++) { ps.setInt(1, i); ps.addBatch(); } ps.executeBatch(); conn.commit();
ps.close(); // Move this line into a finally block. conn.close();
|
JDBC 3.0规范提到, JDBC驱动会在调用PreparedStatement#executeBatch时做commit。需要小心的是批处理失败的情况:如果关闭auto commit,当发生错误时,可以调用rollback进行回滚,也可以调用commit提交成功执行的那部分修改;但如果打开auto commit,如何处理由实现(驱动)决定。因此,在执行批处理时,应当总是关闭auto commit。
Oracle 9i特性中,给出了一些最佳实践,包括:
1. 在执行批处理时,总是关闭auto commit;
2. Batch的大小应保持在10左右;
Oracle后续的版本(10g/11g),也要求关闭auto commit。但是,在11g中,推荐的batch大小介于50到100之间。
Oracle recommends you to keep the batch sizes in the general range of 50 to 100. This is because though the drivers support larger batches, they in turn result in a large memory footprint with no corresponding increase in performance. Very large batches usually result in a decline in performance compared to smaller batches. |
Oracle在批处理上还有些其它限制,具体可以参考文后的链接。我们可能不需要关注这样的细节,最好咨询DBA。
二.update和executeBatch返回值
iBatis SqlMap文档提到,批处理执行时,JDBC驱动可以不返回更新的记录数。所以在批处理中,不要依赖update、delete、updateBatch等方法的返回值。
Note that it is entirely legal for the JDBC driver to fail to return the number of records updated in a batch - in which case the executeBatch() method will return 0 even though records have been updated. The Oracle driver is a good example of a driver that behaves this way. |
但是,如果一条insert语句的sqlMap定义了selectKey语句,批处理中的insert仍然会正确返回主键值,因为SelectKeyStatement是在批处理外进行的。请参考SqlMapExecutorDelegate#insert。
三.batch与事务
批处理必须总是包裹在一个显式的事务中,否则iBatis会无视batch,逐条执行。请参考SqlMapExecutorDelegate#insert。
A batch should ALWAYS be nested inside an explicit transaction. If you fail to use an explicit transaction, then iBATIS will execute each statement individually as if you had never started a batch. |
在使用到批处理时,我们通常在DAO这样写:
getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException { executor.startBatch(); for (QuotationItemTemplateDO template : quotationItemTemplates) { executor.insert("MS-CREATE-QUOT-ITEM-TEMPLATE", template); } executor.executeBatch(); return null; } }); |
SqlMapClientTemplate会自动包裹一个UserProvidedTransaction,不过遗憾的是,这个事务不会关闭auto commit。所以我们还是需要在Biz层包裹一个Spring管理的事务。请参考DataSourceTransactionManager#doBegin,这个方法关闭了auto commit。
四.参考
² JDBC 3.0 Spec: http://cds.sun.com/is-bin/INTERSHOP.enfinity/WFS/CDS-CDS_Developer-Site/en_US/-/USD/VerifyItem-Start/jdbc-3_0-fr-spec.pdf?BundledLineItemUUID=TBGJ_hCujZcAAAEpc8FCxpJr&OrderID=ugmJ_hCukvUAAAEpZcFCxpJr&ProductID=eKnACUFBKakAAAEYLrU5AXiq&FileName=/jdbc-3_0-fr-spec.pdf
² Oracle 9i Feature:
http://www.oracle.com/technology/products/oracle9i/daily/jun07.html
² Oracle 10g – Performance Extensions:
http://download.oracle.com/docs/cd/B19306_01/java.102/b14355/oraperf.htm#i1059053
² Oracle 11g – Performance Extensions:
http://download.oracle.com/docs/cd/E11882_01/java.112/e10589/oraperf.htm#i1056232
² iBatis SqlMap Document:
http://svn.apache.org/repos/asf/ibatis/java/ibatis-2/trunk/ibatis-2-docs/en/iBATIS-SqlMaps-2_en.pdf