最近遇到这样一个客户需求:需要向数据库里面一次插入几万条数据。系统的Persistence层用的是ibatis,
事务是通过spring管理。之前都是少量数据的操作,所以都是按照以下方式插入的:
class Service extends SqlMapClientDaoSupport
{
public void insert(...)
{
getSqlMapClientTemplate().insert(..);
}
}
但是数据量大时,速度奇慢。于是找时间读了一下ibatis的源码,终于发现了问题所在,记录如下:
首先,过跟踪代码,发现sql的相关操作都有这样一个入口:
public Object execute(SqlMapClientCallback action){ action.doInSqlMapClient(session);....}
上面的session是SqlMapSession的一个实例,而SqlMapSession继承了SqlMapExecutor接口,实际上以上的代码最终还是通过SqlMapExecutor的对应方法来实现(比如:session.insert(..)).
于是继续追踪SqlMapSession的实现类:SqlMapSessionImpl。发现这个类的所有JDBC操作都是通过代理类SqlMapExecutorDelegate来实现的(这个代理类比SqlExecutor多了事务管理的配置:有一个TransactionManager)。这个代理类在每个单独的操作时,都先有这样一条语句:
trans = getTransaction(session);
autoStart = trans == null;
trans = autoStartTransaction(session, autoStart, trans);
上述代码通过判断sutoStart来决定是不是开启一个事务。而autoStart是通过判断当前是不是已经有打开的事务
来赋值的。那么就可以理解了:如果当前操作没有在事务下面,那么自动开启(取出)一个事务;如果已经有了事务,那么 直接使用当前事务。如果要进行批量操作,那么就必须在调用之前开启一个事务。所以就简单了:
public Object operate(final List<CardInfo> cardsToAdd, final List<AcctInfo> acctsToAdd, final List<AcctInfo> acctsToUpdate) throws DataAccessException{
Object obj=this.getSqlMapClientTemplate().execute(new SqlMapClientCallback(){
public Object doInSqlMapClient(SqlMapExecutor executor)
{
try{
getSqlMapClient().startTransaction();
executor.startBatch();...
后面的startBatch语句是通过使用jdbc的批处理来提高效率。这样就能顺利执行同一个事务下的批量操作了(注意:如果在批量startBatch之前没有开启事务,批处理是无效的)。
就总结到这里吧,请光临的朋友多多指教。