背景:调试模式下,单步运行一个查询订单协议操作,记录了ibatis框架的执行动作,侧面剖析其原理。
一、简介:
1. dal 层的dao接口实现类通常会继承SqlMapClientDaoSupport。spring容器在初始化一个dao bean实例时,通常会注入两块信息DataSource(数据源)和sqlMapClient(主要是sql语句),这两块信息会封装到SqlMapClientTemplate
2. 其中数据源的实例通常采用apache的开源项目dbcp
代码配置如下:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="xxxx" /> <property name="username"><value>xxxx</value></property> <property name="password"><value>xxxxx</value></property> <property name="maxActive"><value>20</value></property> <property name="initialSize"><value>1</value></property> <property name="maxWait"><value>60000</value></property> <property name="maxIdle"><value>20</value></property> <property name="minIdle"><value>3</value></property> <property name="removeAbandoned"><value>true</value></property> <property name="removeAbandonedTimeout"><value>180</value></property> <property name="connectionProperties"><value>clientEncoding=GBK</value></property> </bean>各配置参数的具体含义可参照: dbcp基本配置和重连配置
3. sqlMapClient
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="configLocation"> <value>classpath:sqlmap.xml</value> </property> </bean> <bean id="sqlMapClientTddl" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="dataSource" ref="tGroupDataSource" /> <property name="configLocation" value="classpath:sqlmap.xml"/> </bean>sqlMapClient,主要是借助于实现FactoryBean和InitializingBean两个接口,加载sql.xml文件资源信息,得到sqlMapClient组件
注:上面的sqlMapClient默认不配置数据源,后面的SqlMapClientTemplate优先从全局变量中取,如果没有再从sqlMapClient中查找。
public DataSource getDataSource() { DataSource ds = super.getDataSource(); return (ds != null ? ds : this.sqlMapClient.getDataSource()); }构造 sqlMapClient组件的 代码块。
public void afterPropertiesSet() throws Exception { if (this.lobHandler != null) { // Make given LobHandler available for SqlMapClient configuration. // Do early because because mapping resource might refer to custom types. configTimeLobHandlerHolder.set(this.lobHandler); } try { this.sqlMapClient = buildSqlMapClient(this.configLocations, this.mappingLocations, this.sqlMapClientProperties); // Tell the SqlMapClient to use the given DataSource, if any. if (this.dataSource != null) { TransactionConfig transactionConfig = (TransactionConfig) this.transactionConfigClass.newInstance(); DataSource dataSourceToUse = this.dataSource; if (this.useTransactionAwareDataSource && !(this.dataSource instanceof TransactionAwareDataSourceProxy)) { dataSourceToUse = new TransactionAwareDataSourceProxy(this.dataSource); } transactionConfig.setDataSource(dataSourceToUse); transactionConfig.initialize(this.transactionConfigProperties); applyTransactionConfig(this.sqlMapClient, transactionConfig); } } finally { if (this.lobHandler != null) { // Reset LobHandler holder. configTimeLobHandlerHolder.set(null); } } }
4. SqlMapExecutor
该接口是对SQL操作行为的抽象,提供了SQL单条执行和批处理涉及的所有操作方法
5. SqlMapTransactionManager
该接口是对事务行为的抽象,提供了事务执行过程中涉及的所有方法。
6. SqlMapClient
该接口定位是SQL执行客户端,是线程安全的,用于处理多个线程的sql执行。它继承了上面两个接口,这意味着该接口具有SQL执行和事务处理的能力,该接口的核心实现类是SqlMapClientImpl。
7. SqlMapSession
该接口在继承关系上和SqlMapClient一致,但它的定位是保存单线程sql执行过程的session信息。该接口的核心实现类是SqlMapSessionImpl
8. MappedStatement
该接口定位是单条SQL执行时的上下文环境信息,如SQL标识、SQL、参数信息、返回结果、操作行为等。
9. ParameterMap/ResultMap
该接口用于在SQL执行的前后提供参数准备和执行结果集的处理。
整体类图:
二、具体调用
接下来就到了数据持久层的代码调用,所有的数据库DML操作(增、删、改、查)都是借助于SqlMapClientTemplate来实现的。
public OrderEnsureProtocolDO getOrderEnsureProtocolByOrderId(Long orderId) { if (orderId == null) { return null; } return (OrderEnsureProtocolDO) this.getSqlMapClientTemplate().queryForObject("MS-FIND-ORDERENSUREPROTOCOL-BY-ORDERID", orderId); }
如果一次执行的sql较多,我们会采用批处理的形式
public void batchDeleteOfferSaleRecord(final List<Long> orderEntryIds) throws Exception { if (orderEntryIds == null || orderEntryIds.size() < 1 || orderEntryIds.size() > 50) { return; } this.getSqlMapClientTemplate().execute(new SqlMapClientCallback() { @Override public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException { executor.startBatch(); for (Long entryId : orderEntryIds) { executor.insert(MS_DELETE_SALE_RECORD, entryId); } return executor.executeBatch(); } }); }
不管采用上面哪种方式,查看源代码会发现,最后都是在调用execute(SqlMapClientCallback action)方法
public Object queryForObject(final String statementName, final Object parameterObject) throws DataAccessException { return execute(new SqlMapClientCallback() { public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException { return executor.queryForObject(statementName, parameterObject); } }); }
public Object execute(SqlMapClientCallback action) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Assert.notNull(this.sqlMapClient, "No SqlMapClient specified"); //获取session信息(SqlMapSessionImpl实例) SqlMapSession session = this.sqlMapClient.openSession(); if (logger.isDebugEnabled()) { logger.debug("Opened SqlMapSession [" + session + "] for iBATIS operation"); } Connection ibatisCon = null; try { Connection springCon = null;//数据库连接 DataSource dataSource = getDataSource(); boolean transactionAware = (dataSource instanceof TransactionAwareDataSourceProxy); try { //获取数据获连接 ibatisCon = session.getCurrentConnection(); if (ibatisCon == null) { springCon = (transactionAware ? dataSource.getConnection() : DataSourceUtils.doGetConnection(dataSource)); //将数据源set到session会话中 session.setUserConnection(springCon); if (logger.isDebugEnabled()) { logger.debug("Obtained JDBC Connection [" + springCon + "] for iBATIS operation"); } } else { if (logger.isDebugEnabled()) { logger.debug("Reusing JDBC Connection [" + ibatisCon + "] for iBATIS operation"); } } } catch (SQLException ex) { throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex); } try { //执行SQL return action.doInSqlMapClient(session); } catch (SQLException ex) { throw getExceptionTranslator().translate("SqlMapClient operation", null, ex); } finally { 省略。。。一系列的关闭工作 } }
SqlMapSessionImpl().queryForObject()的方法很简单,直接交给代理对象SqlMapExecutorDelegate处理(里面注入了很多功能对象,负责具体的sql执行)
public Object queryForObject(String id, Object paramObject) throws SQLException { return delegate.queryForObject(session, id, paramObject); }经过N层重载,最后调用内部的通用方法
入参: id=MS-FIND-ORDERENSUREPROTOCOL-BY-ORDERID paramObject=26749329 public Object queryForObject(SessionScope session, String id, Object paramObject, Object resultObject) throws SQLException { Object object = null; //MappedStatement对象集是上文中提及的初始化方法SqlMapClientFactoryBean.afterPropertiesSet()中,由配置文件构建而成 //调试中的ms为SelectStatement,具体的执行器 MappedStatement ms = getMappedStatement(id); // 用于事务执行 Transaction trans = getTransaction(session); boolean autoStart = trans == null; try { trans = autoStartTransaction(session, autoStart, trans); // 从RequestScope池中获取该次sql执行中的上下文环境RequestScope RequestScope request = popRequest(session, ms); try { // 执行sql object = ms.executeQueryForObject(request, trans, paramObject, resultObject); } finally { pushRequest(request); //归还RequestScope } autoCommitTransaction(session, autoStart); } finally { autoEndTransaction(session, autoStart); } return object; }
public Object executeQueryForObject(RequestScope request, Transaction trans, Object parameterObject, Object resultObject) throws SQLException { try { Object object = null; DefaultRowHandler rowHandler = new DefaultRowHandler(); //执行sql语句 executeQueryWithCallback(request, trans.getConnection(), parameterObject, resultObject, rowHandler, SqlExecutor.NO_SKIPPED_RESULTS, SqlExecutor.NO_MAXIMUM_RESULTS); //结果处理,返回结果 List list = rowHandler.getList(); if (list.size() > 1) { throw new SQLException("Error: executeQueryForObject returned too many results."); } else if (list.size() > 0) { object = list.get(0); } 。。。。。。。。。 }MappedStatement.executeQueryWithCallback()方法包含了参数值映射、sql准备和sql执行等关键过程
protected void executeQueryWithCallback(RequestScope request, Connection conn, Object parameterObject, Object resultObject, RowHandler rowHandler, int skipResults, int maxResults) throws SQLException { //预先封装错误信息,如果报错时便于排查问题 ErrorContext errorContext = request.getErrorContext(); errorContext.setActivity("preparing the mapped statement for execution"); errorContext.setObjectId(this.getId()); errorContext.setResource(this.getResource()); try { //验证入参 parameterObject = validateParameter(parameterObject); //获取SQL对象 Sql sql = getSql(); errorContext.setMoreInfo("Check the parameter map."); // 入参映射 ParameterMap parameterMap = sql.getParameterMap(request, parameterObject); errorContext.setMoreInfo("Check the result map."); //获取结果 ResultMap resultMap = sql.getResultMap(request, parameterObject); request.setResultMap(resultMap); request.setParameterMap(parameterMap); errorContext.setMoreInfo("Check the parameter map."); //获取参数值 Object[] parameters = parameterMap.getParameterObjectValues(request, parameterObject); errorContext.setMoreInfo("Check the SQL statement."); //获取拼装后的sql语句 String sqlString = sql.getSql(request, parameterObject); errorContext.setActivity("executing mapped statement"); errorContext.setMoreInfo("Check the SQL statement or the result map."); RowHandlerCallback callback = new RowHandlerCallback(resultMap, resultObject, rowHandler); //sql执行 sqlExecuteQuery(request, conn, sqlString, parameters, skipResults, maxResults, callback); ....省略 }
public void executeQuery(RequestScope request, Connection conn, String sql, Object[] parameters, int skipResults, int maxResults, RowHandlerCallback callback) throws SQLException { ...省略 PreparedStatement ps = null; ResultSet rs = null; setupResultObjectFactory(request); try { errorContext.setMoreInfo("Check the SQL Statement (preparation failed)."); Integer rsType = request.getStatement().getResultSetType(); //初始化PreparedStatement,设置sql、参数值等 if (rsType != null) { ps = prepareStatement(request.getSession(), conn, sql, rsType); } else { ps = prepareStatement(request.getSession(), conn, sql); } setStatementTimeout(request.getStatement(), ps); Integer fetchSize = request.getStatement().getFetchSize(); if (fetchSize != null) { ps.setFetchSize(fetchSize.intValue()); } errorContext.setMoreInfo("Check the parameters (set parameters failed)."); request.getParameterMap().setParameters(request, ps, parameters); errorContext.setMoreInfo("Check the statement (query failed)."); //执行 ps.execute(); errorContext.setMoreInfo("Check the results (failed to retrieve results)."); //结果集处理 rs = handleMultipleResults(ps, request, skipResults, maxResults, callback); 。。。省略 }