`
budairenqin
  • 浏览: 202224 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

iBatis batch处理那些事

阅读更多
   昨天应同事要求在框架中(Spring+iBatis2.3.4)加入Batch处理,于是满足之,由于需要更灵活并且不想为批量插入、批量更新、批量删除等操作单独写对应的方法,于是写了这样的一个方法
	public Object batchExecute(final CallBack callBack) {
		Object result = getSqlMapClientTemplate().execute(new SqlMapClientCallback<Object>() {

			@Override
			public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException {
				executor.startBatch();
				Object obj = callBack.execute(new IbatisSqlExecutor(executor));
				executor.executeBatch();
				return obj;
			}
		});
		return result;
	}

不想将SqlMapExecutor侵入到业务代码中,于是又有了如下3个类,在今天的主题中不是关键,可以忽略,只是为了将代码贴完整
public interface SqlExecutor {
	Object insert(String id, Object parameterObject) throws SQLException;
	Object insert(String id) throws SQLException;
	int update(String id, Object parameterObject) throws SQLException;
	int update(String id) throws SQLException;
	int delete(String id, Object parameterObject) throws SQLException;
	int delete(String id) throws SQLException;
	Object queryForObject(String id, Object parameterObject) throws SQLException;
	Object queryForObject(String id) throws SQLException;
	Object queryForObject(String id, Object parameterObject, Object resultObject) throws SQLException;
}

class IbatisSqlExecutor implements SqlExecutor {
	private SqlMapExecutor executor;
	IbatisSqlExecutor(SqlMapExecutor executor) {
		this.executor = executor;
	}
	@Override
	public Object insert(String id, Object parameterObject) throws SQLException {
		return executor.insert(id, parameterObject);
	}
	// 剩下的就省略了,和insert都类似
}

public interface CallBack {
		Object execute(SqlExecutor executor);
}


然后执行了一个类似以下伪代码行为的操作:
getDao().batchExecute(new CallBack() {
        @Override
        public Object execute(SqlExecutor executor) {
                for (int i = 0; i < 10000; ++i) {
                        // 注意每个sql_id的sql语句都是不相同的
                        executor.insert("id1", obj1);
                        executor.insert("id2", obj2);
                        // ...
                        executor.insert("idn", objn);
                }
                return null;
        }
});

再然后...尼玛不但速度没变快还异常了,原因竟然是生成了太多的PreparedStatement,你妹不是批处理吗?不是应该一个sql只有一个PreparedStatement吗?
跟踪iBatis代码,发现了iBatis是这样处理的
// 以下代码来自com.ibatis.sqlmap.engine.execution.SqlExecutor$Batch

    public void addBatch(StatementScope statementScope, Connection conn, String sql, Object[] parameters) throws SQLException {
      PreparedStatement ps = null;
// 就是它:currentSql
      if (currentSql != null && currentSql.equals(sql)) {
        int last = statementList.size() - 1;
        ps = (PreparedStatement) statementList.get(last);
      } else {
        ps = prepareStatement(statementScope.getSession(), conn, sql);
        setStatementTimeout(statementScope.getStatement(), ps);
// 就是它:currentSql
        currentSql = sql;
        statementList.add(ps);
        batchResultList.add(new BatchResult(statementScope.getStatement().getId(), sql));
      }
      statementScope.getParameterMap().setParameters(statementScope, ps, parameters);
      ps.addBatch();
      size++;
    }

不细解释了,只看currentSql这个实例变量就知道了,如果sql与前一次不同那么会新建一个PreparedStatement,所以刚才的伪代码应该这样写:
getDao().batchExecute(new CallBack() {
        @Override
        public Object execute(SqlExecutor executor) {
                for (int i = 0; i < 10000; ++i) {
                        executor.insert("id1", obj1);
                }
                for (int i = 0; i < 10000; ++i) {
                        executor.insert("id2", obj2);
                }
                // ...你就反复写for循环吧
                return null;
        }
});

很不爽是不?反正我是决了一个定,改iBatis的源码
改源码最好这么来:
1.复制对应类的源码到工程下,本例中要复制的是com.ibatis.sqlmap.engine.execution.SqlExecutor
注意要保持包名,其实是类的全限定名称要一样哇,这样根据ClassLoader的类加载机制会先加载你工程中的SqlExecutor而不加载iBatis jar包中的对应SqlExecutor
如图:



2.改之,只改static class Batch这个内部类即可,策略是去掉currentSql,将PreparedStatement放入HashMap
如下:
public void addBatch(StatementScope statementScope, Connection conn, String sql, Object[] parameters) throws SQLException {
			PreparedStatement ps = statementMap.get(sql);
			if (ps == null) {
				ps = prepareStatement(statementScope.getSession(), conn, sql);
				setStatementTimeout(statementScope.getStatement(), ps);
				statementMap.put(sql, ps);
				batchResultMap.put(sql, new BatchResult(statementScope.getStatement().getId(), sql));
			}
			statementScope.getParameterMap().setParameters(statementScope, ps, parameters);
			ps.addBatch();
			++size;
		}


下面贴出修改后完整的代码,方便有同样需求的同学修改,只贴出内部类com.ibatis.sqlmap.engine.execution.SqlExecutor$Batch,com.ibatis.sqlmap.engine.execution.SqlExecutor没有做出任何修改
	private static class Batch {

		private Map<String, PreparedStatement> statementMap = new HashMap<String, PreparedStatement>();
		private Map<String, BatchResult> batchResultMap = new HashMap<String, BatchResult>();
		private int size;

		/**
		 * Create a new batch
		 */
		public Batch() {
			size = 0;
		}

		/**
		 * Getter for the batch size
		 * @return - the batch size
		 */
		@SuppressWarnings("unused")
		public int getSize() {
			return size;
		}

		/**
		 * Add a prepared statement to the batch
		 * @param statementScope - the request scope
		 * @param conn - the database connection
		 * @param sql - the SQL to add
		 * @param parameters - the parameters for the SQL
		 * @throws SQLException - if the prepare for the SQL fails
		 */
		public void addBatch(StatementScope statementScope, Connection conn, String sql, Object[] parameters) throws SQLException {
			PreparedStatement ps = statementMap.get(sql);
			if (ps == null) {
				ps = prepareStatement(statementScope.getSession(), conn, sql);
				setStatementTimeout(statementScope.getStatement(), ps);
				statementMap.put(sql, ps);
				batchResultMap.put(sql, new BatchResult(statementScope.getStatement().getId(), sql));
			}
			statementScope.getParameterMap().setParameters(statementScope, ps, parameters);
			ps.addBatch();
			++size;
		}

		/**
		 * TODO (Jeff Butler) - maybe this method should be deprecated in some release,
		 * and then removed in some even later release. executeBatchDetailed gives
		 * much more complete information.
		 * <p/>
		 * Execute the current session's batch
		 * @return - the number of rows updated
		 * @throws SQLException - if the batch fails
		 */
		public int executeBatch() throws SQLException {
			int totalRowCount = 0;
			for (Map.Entry<String, PreparedStatement> iter : statementMap.entrySet()) {
				PreparedStatement ps = iter.getValue();
				int[] rowCounts = ps.executeBatch();
				for (int j = 0; j < rowCounts.length; j++) {
					if (rowCounts[j] == Statement.SUCCESS_NO_INFO) {
						// do nothing
					} else if (rowCounts[j] == Statement.EXECUTE_FAILED) {
						throw new SQLException("The batched statement at index " + j + " failed to execute.");
					} else {
						totalRowCount += rowCounts[j];
					}
				}
			}
			return totalRowCount;
		}

		/**
		 * Batch execution method that returns all the information
		 * the driver has to offer.
		 * @return a List of BatchResult objects
		 * @throws BatchException (an SQLException sub class) if any nested
		 *             batch fails
		 * @throws SQLException if a database access error occurs, or the drive
		 *             does not support batch statements
		 * @throws BatchException if the driver throws BatchUpdateException
		 */
		@SuppressWarnings({ "rawtypes", "unchecked" })
		public List executeBatchDetailed() throws SQLException, BatchException {
			List answer = new ArrayList();
			int i = 0;
			for (String sql : statementMap.keySet()) {
				BatchResult br = batchResultMap.get(sql);
				PreparedStatement ps = statementMap.get(sql);
				try {
					br.setUpdateCounts(ps.executeBatch());
				} catch (BatchUpdateException e) {
					StringBuffer message = new StringBuffer();
					message.append("Sub batch number ");
					message.append(i + 1);
					message.append(" failed.");
					if (i > 0) {
						message.append(" ");
						message.append(i);
						message.append(" prior sub batch(s) completed successfully, but will be rolled back.");
					}
					throw new BatchException(message.toString(), e, answer, br.getStatementId(), br.getSql());
				}
				++i;
				answer.add(br);
			}
			return answer;
		}

		/**
		 * Close all the statements in the batch and clear all the statements
		 * @param sessionScope
		 */
		public void cleanupBatch(SessionScope sessionScope) {
			for (Map.Entry<String, PreparedStatement> iter : statementMap.entrySet()) {
				PreparedStatement ps = iter.getValue();
				closeStatement(sessionScope, ps);
			}
			statementMap.clear();
			batchResultMap.clear();
			size = 0;
		}
	}
  • 大小: 21.3 KB
分享到:
评论

相关推荐

    ibatis应对批量update

    为了解决这个问题,ibatis(现已更名为MyBatis)提供了一种支持批量更新的机制,能够显著提高数据处理的速度。 #### 批量Update背景与问题 在实际应用中,经常会遇到需要批量更新数据库中的数据的情况,例如:商品...

    ibatis乱码解决方法(ibatis独立)

    - 在Ibatis的配置文件`SqlMapConfig.xml`中,可以设置`&lt;settings&gt;`标签内的`defaultExecutorType`属性为`BATCH`或`SIMPLE`,以确保每次执行的SQL都是独立的,避免因缓存导致的编码问题。 - 同时,确保你的项目编码...

    ibatis-2.3.

    10. **Batch Operations**: iBATIS 支持批量操作,如插入、更新和删除,可以提高数据处理效率。 从描述 "ibatis-2.3.ibatis-2.3.ibatis-2.3" 看,似乎是在强调这个版本,但并没有提供额外的信息。标签 "ibat" 显然...

    ibatis配置文件模板

    - `defaultExecutorType`: 默认执行器类型,如SIMPLE、REUSE或BATCH。 - `cacheEnabled`: 是否开启全局缓存,默认为true。 2. **SqlMapConfig.xml** 这是Ibatis的核心配置文件,它定义了数据源、事务管理器、...

    ibatis 完美例子 一对多 批处理 事务 和 spring struts2集成

    在Ibatis中,可以通过设置SqlSession的flushCache和useCache属性,以及使用批处理执行器ExecutorType.BATCH,来实现批量插入、更新或删除。例如,在插入1万条数据时,将这些操作放在同一个SqlSession中,而不是逐一...

    ibatis批量存储

    使用Ibatis的批处理,首先需要开启SqlSession的自动提交,然后调用SqlSession的batch()方法进入批处理模式,接着执行多次insert、update或delete操作,最后调用commit()方法提交事务。这种方式避免了频繁的数据库...

    ibatis简单实例

    4. **Executor**: 执行器,负责处理SQL的执行,有Simple、Reused和Batch三种模式。 5. **ParameterHandler**: 处理参数对象,将Java对象转换为SQL语句中的参数。 6. **ResultSetHandler**: 将查询结果处理成Java...

    ibatis2.X升级mybatis3.X之曲径通幽处

    动态SQL是Ibatis的一大亮点,但在3.x中,动态SQL表达式变得更加强大,可以更好地处理复杂的查询条件。例如,`&lt;if&gt;`、`&lt;choose&gt;`、`&lt;when&gt;`、`&lt;otherwise&gt;`等标签的使用,让SQL语句可以根据条件动态生成,提高了代码...

    SpringBatch批处理 刘相编

    以及Spring Batch框架中经典的三步走策略:数据读、数据处理和数据写,详尽地介绍了如何对CVS格式文件、JSON格式文件、XML文件、数据库和JMS消息队列中的数据进行读操作、处理和写操作,对于数据库的操作详细介绍了...

    Ibatis增删改查

    Ibatis 是一款轻量级的Java持久层框架,它与Hibernate和JPA等ORM框架不同,Ibatis 更注重SQL的自由度,允许开发者直接编写SQL语句,将SQL与Java代码解耦,提供了更高的灵活性。在本文中,我们将深入探讨如何使用...

    ibatis api 英文文档

    11. **Batch Operations**:iBATIS提供了批处理功能,可以一次性提交多个插入、更新或删除操作,提升批量数据处理的效率。 12. **Error Handling**:当SQL执行出错时,iBATIS会抛出异常,提供错误信息帮助定位问题...

    ibatis

    - iBatis 提供了两种执行器(Executor)模式:Simple Executor 和 Batch Executor,分别用于单个语句的执行和批处理。 7. **插件支持** - iBatis 允许自定义插件,可以拦截 SQL 执行过程中的各个步骤,进行额外的...

    axis1.4+ibatis2.3开发webservice服务[图解]

    尽管Axis 2提供了一些改进和新特性,但在实际项目部署过程中发现,Axis 2在处理发布的服务aar包中的iBatis `sqlmapconfig.xml`配置文件时存在问题,导致配置文件无法被正确识别和加载。因此,在这种情况下,建议选择...

    ibatis案例

    Ibatis,一个轻量级的Java持久层框架,是许多开发者在处理数据库操作时的首选工具。本案例旨在为初学者提供一个清晰的视角,帮助理解Ibatis的基本概念、配置和使用方法。以下是对Ibatis案例开发的一些核心知识点的...

    基于Spring+Ibatis的安全线程实现

    ExecutorType.BATCH则适合批量操作,能有效减少数据库交互次数,提升多线程环境下的性能。 7. **同步锁机制**:在特定场景下,如全局唯一ID生成,可以使用synchronized关键字或者Lock接口(如ReentrantLock)来实现...

    ibatis 批量 增删改查

    Ibatis,作为一个轻量级的Java持久层框架,它提供了灵活的方式来处理这些操作,使得开发者可以避免过于繁琐的手动SQL编写工作。本篇文章将深入探讨Ibatis如何实现批量增删改查,以及相关的最佳实践。 首先,批量...

    iBatis操作

    ### iBatis批量操作 #### 一、简介 在企业级应用开发中,数据库操作是不可或缺的一部分。为了提高效率和减少资源消耗,批量处理成为了一种常用的技术手段。本文将介绍如何利用iBatis框架进行批量添加、修改、删除...

    IBatis框架

    **IBatis 框架详解** ...总的来说,IBatis 是一种灵活且高效的持久层解决方案,适合那些需要精细控制 SQL 的项目。通过熟练掌握 IBatis,开发者可以有效地管理数据库操作,提高代码可读性和维护性。

    ibatis的批量插入DAO实例

    通过这种方式,Ibatis可以方便地处理批量插入操作。然而,为了优化性能,我们还需要关注JDBC的批处理设置。默认情况下,Ibatis会自动提交每个单独的INSERT语句,这可能并不是最高效的。可以在MyBatis的配置文件中...

Global site tag (gtag.js) - Google Analytics