为了实现日志批处理提交,这几天详细研究了ibatis的事务和批处理。
直接上代码,然后说结论吧。spring版本3.1 + ibatis2.3.4
配置
<bean id="log.sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation"
value="classpath:config/db/ibatis/log-sqlmap-config.xml" />
<property name="dataSource" ref="log.dataSource" />
</bean>
<bean id="log.transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="log.dataSource" />
</bean>
<!-- enable transaction demarcation with annotations -->
<tx:annotation-driven />
<!-- DAO配置 -->
<bean id="logDAO" class="com.X.LogDAO">
<property name="sqlMapClient" ref="log.sqlMapClient" />
</bean>
java代码:
/***
* ibatis的批处理必须放在事务内,此处事务的作用只是起着开启批处理的作用, 实际上每个sql一个事务
* 目的:使用批处理,而不使用事务,故选择此方法
*
*/
public void batchSave(Queue<BaseLog> logQueue) {
if (logQueue == null)
return;
long startTime = System.currentTimeMillis();
int queueSize = logQueue.size();
if (queueSize == 0)
return;
SqlMapClientTemplate sqlMapClientTemplate = getSqlMapClientTemplate();
SqlMapClient sqlMapClient = sqlMapClientTemplate.getSqlMapClient();
try {
sqlMapClient.startTransaction();
sqlMapClient.startBatch();
int insertNum = 0;
for (int i = 0; i < queueSize; i++) {
BaseLog log = logQueue.poll();
if (log == null)
break;
insertNum++;
String saveMethod = "insert" + log.getClass().getSimpleName();
sqlMapClient.insert(saveMethod, log);
if (insertNum % BATCH_SIZE == 0) {
sqlMapClient.executeBatch();
sqlMapClient.startBatch();
}
}
sqlMapClient.executeBatch();
sqlMapClient.commitTransaction();
long logTime = System.currentTimeMillis() - startTime;
logger.info("[记录日志] [大小:" + insertNum + "] [时间:" + logTime
+ "] [succ]");
} catch (SQLException e) {
logger.error("[记录日志] [fail]", e);
} finally {
try {
sqlMapClient.endTransaction();
} catch (SQLException e) {
logger.error("[EndLogTransaction] [error]", e);
}
}
}
/**
* spring首选批处理提交方式,失败的话,会自动回滚
*
* @param logQueue
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
@Transactional(value = "log.transactionManager")
public void batchSaveX(final Queue<BaseLog> logQueue) {
// 执行回调
getSqlMapClientTemplate().execute(new SqlMapClientCallback() {
// 实现回调接口
public Object doInSqlMapClient(SqlMapExecutor executor)
throws SQLException {
int queueSize = logQueue.size();
if (queueSize == 0)
return 0;
long startTime = System.currentTimeMillis();
int count = 0, total = 0;
// 开始批处理
executor.startBatch();
for (int i = 0; i < queueSize; i++) {
BaseLog baseLog = logQueue.poll();
// 插入操作
String saveMethod = "insert"
+ baseLog.getClass().getSimpleName();
executor.insert(saveMethod, baseLog);
count++;
if (count % BATCH_SIZE == 0) {
total += executor.executeBatch();
executor.startBatch();
}
}
// 执行批处理
total += executor.executeBatch();
long logTime = System.currentTimeMillis() - startTime;
logger.info("[记录日志2] [大小:" + total + "] [时间:" + logTime
+ "] [succ]");
return new Integer(total);
}
});
}
总结: 上面第一种方式中的事务,只起着开启批处理的作用,没有其他作用。另外网上有人推荐在第一种方法上添加@Transational来开启事务,这种方法能开启事务,的确能打开事务,但是没批处理,主要是spring接管的事务会话,不能自己操作的sqlMapClient很好的衔接起来。同时事务侵入代码+不能回滚,所以要是采用事务的话,还是不要采用这种方法。要
采用事务的话,最好采用上面第二方法。
log4j配置文件,方便前端监控,事务和批处理的情况
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.springframework.orm">
<level value="debug" />
</logger>
<logger name="org.springframework.jdbc">
<level value="debug" />
</logger>
数据库端,采用mysql的话可以mysqlbinlog查看二进制日志查看事务提交情况,bingin...commit代表一个事务