ibatis源码分析
背景
本文基于调试模式下的单步运行记录,通过分析一个查询订单协议的操作,侧面剖析 iBATIS 框架的执行动作与原理。
一、核心组件简介
1. DAO 层与 SqlMapClientDaoSupport
DAL 层的 DAO 接口实现类通常会继承 SqlMapClientDaoSupport。Spring 容器在初始化一个 DAO Bean 实例时,通常会注入两块信息:DataSource(数据源)和 SqlMapClient(主要是 SQL 语句)。这两块信息会被封装到 SqlMapClientTemplate 中。
2. 数据源配置 (DataSource)
其中数据源的实例通常采用 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 单条执行和批处理涉及的所有操作方法。 - SqlMapTransactionManager
该接口是对事务行为的抽象,提供了事务执行过程中涉及的所有方法。 - SqlMapClient
该接口定位是 SQL 执行客户端,是线程安全的,用于处理多个线程的 SQL 执行。它继承了上面两个接口,这意味着该接口具有 SQL 执行和事务处理的能力,该接口的核心实现类是SqlMapClientImpl。 - SqlMapSession
该接口在继承关系上和SqlMapClient一致,但它的定位是保存单线程 SQL 执行过程的 Session 信息。该接口的核心实现类是SqlMapSessionImpl。 - MappedStatement
该接口定位是单条 SQL 执行时的上下文环境信息,如 SQL 标识、SQL、参数信息、返回结果、操作行为等。 - ParameterMap / ResultMap
该接口用于在 SQL 执行的前后提供参数准备和执行结果集的处理。
整体类图:
二、核心调用流程分析
接下来到了数据持久层的代码调用,所有的数据库 DML 操作(增、删、改、查)都是借助于 SqlMapClientTemplate 来实现的。
1. 单条查询示例
public OrderEnsureProtocolDO getOrderEnsureProtocolByOrderId(Long orderId) {
if (orderId == null) {
return null;
}
return (OrderEnsureProtocolDO) this.getSqlMapClientTemplate().queryForObject(
"MS-FIND-ORDERENSUREPROTOCOL-BY-ORDERID", orderId);
}2. 批处理示例
如果一次执行的 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();
}
});
}3. 内部执行流程 (execute)
不管采用上面哪种方式,查看源代码会发现,最后都是在调用 execute(SqlMapClientCallback action) 方法。
queryForObject 内部封装:
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);
}
});
}核心 execute 方法:
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 {
// 省略。。。一系列的关闭工作
}
}
}4. 深层调用链 (Delegate -> MappedStatement -> SqlExecutor)
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;
}接下来由 MappedStatement.executeQueryForObject() 来执行:
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);
// ....省略
}
}最后调用 com.ibatis.sqlmap.engine.execution.SqlExecutor.executeQuery:
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);
// 。。。省略
}
}说明
版本时效性说明:本文基于 iBATIS 框架进行分析。iBATIS 项目已于 2010 年更名为 MyBatis,且 Spring 对 iBATIS 的支持类(如 SqlMapClientDaoSupport)在现代 Spring 版本中已废弃。文中涉及的配置与源码逻辑适用于旧版 iBATIS 2.x 环境,新开发项目建议直接使用 MyBatis 或 MyBatis-Plus。 版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/ibatis-yuan-ma-fen-xi.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。