mybatis源码解析(一)-开篇
mybatis源码解析(二)-加载过程
mybatis源码解析(三)-SqlSession.selectOne类似方法调用过程
转载请标明出处:
http://blog.csdn.net/bingospunky/article/details/79202721
本文出自马彬彬的博客
上篇博客中介绍了mybatis的加载过程,这篇博客介绍一下org.apache.ibatis.session.SqlSession的增、删、改、查方法是怎么实现的。我们使用mybatis时,基本都是使用Mapper进行增、删、改、查操作,但是SqlSession也给了我们方法来完成相似的功能。从源码的角度去看,Mapper调用了SqlSession,所以研究SqlSession是很有必要的,且是研究Mapper的基础。
org.apache.ibatis.session.SqlSession.selectOne方法
使用org.apache.ibatis.session.SqlSession.selectOne类似方法时,主要过程如下面代码:
Code 1
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
第4行,就从org.apache.ibatis.session.Configuration里的protected final Map
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
第2行就是通过MappedStatement还有查询参数来生成BoundSql。BoundSql就是对sql的包装,包含sql语句,参数,List(参数的mapping)。生成BoundSql的核心代码如下:
Code 3
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(this.configuration, parameterObject);
this.rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
Iterator i$ = context.getBindings().entrySet().iterator();
while(i$.hasNext()) {
Entry<String, Object> entry = (Entry)i$.next();
boundSql.setAdditionalParameter((String)entry.getKey(), entry.getValue());
}
return boundSql;
}
生成BoundSql的过程是比较复杂的。第2行构造DynamicContext,并且把参数传递进去,第3行把sql传递进DynamicContext,DynamicContext中的sql语句是包含#{xxx}的。第6行生成SqlSource,SqlSource是个接口,只有一个方法BoundSql getBoundSql(Object var1);,所以SqlSource就是来生成BoundSql的,SqlSource里包含sql和private List parameterMappings;,这里的sql就把占位符换成了?,ParameterMapping的个数和占位符一样多,ParameterMapping这个类包含了传递进去的参数是如何解析的,就像我们写sql时,#{xxx}里面会加上jdbcType等属性。第7行通过sqlSource和传递进来的参数(Map)生成BoundSql,这一行的代码比较简单,直接new一个BoundSql就行,因为BoundSql需要的内容已经在上面几行都凑出来了。其余代码忽略。
再看Code 2,第3行是缓存相关的,我们先忽略掉缓存相关的,后面也会先忽略掉缓存。第4行执行sql的代码如下:
Code 4
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
第5、6行没有复杂逻辑,忽略掉;第7行是生成Statement;第8行是执行Statement,然后解析ResultSet生成对应的Bean。第7、8行的内容是很复杂的,有很多的细节,后面也只是记录主要的过程,第7行生成Statement的主要代码如下:
Code 5
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection);
handler.parameterize(stmt);
return stmt;
}
获取Statement的过程分为两步:第一步通过sql(包含?的)创建PreparedStatement,代码第3行所完成的;第二部给这个Statement设置参数,代码第4行所完成的,设置参数的过程大体上就是遍历List parameterMappings,在传递进来的参数中获取到适当的参数,然后设置到Statement中。这儿可以关注一点,给Statement设置参数的时候需要根据不同的参数类型调用不同的setXXX方法,这是根据参数的Java类型和JdbcType找到合适的TypeHadler,然后使用TypeHadler给Statement参数赋值。关于TypeHadler具体描述可以参考后面内容。
Code 4中第8行执行Statement的代码就一句话,和解析结果的代码是分开的,这里不贴执行Statement的代码了,解析ResultSet的主要代码如下:
Code 6
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
List<Object> multipleResults = new ArrayList();
int resultSetCount = 0;
ResultSetWrapper rsw = this.getFirstResultSet(stmt);
List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
this.validateResultMapsCount(rsw, resultMapCount);
while(rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
rsw = this.getNextResultSet(stmt);
this.cleanUpAfterHandlingResultSet();
++resultSetCount;
}
String[] resultSets = this.mappedStatement.getResulSets();
if (resultSets != null) {
while(rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
}
rsw = this.getNextResultSet(stmt);
this.cleanUpAfterHandlingResultSet();
++resultSetCount;
}
}
return this.collapseSingleResultList(multipleResults);
}
第5行构造了ResultSetWrapper,ResultSetWrapper是对ResultSet的包装,生成了一些column的信息,有column的name、jdbcType、className。
第6行获取了List resultMaps,一般就一个,一般可以再xxxMapper.xml文件的resultMap配置。
第9行的循环次数是List resultMaps的个数,不是结果集的个数,这里不要搞混了。
第11行实现从ResultSet到Bean的转化,转化一个Bean的核心代码如下:
Code 7
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
ResultLoaderMap lazyLoader = new ResultLoaderMap();
Object resultObject = this.createResultObject(rsw, resultMap, lazyLoader, (String)null);
if (resultObject != null && !this.typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
MetaObject metaObject = this.configuration.newMetaObject(resultObject);
boolean foundValues = !resultMap.getConstructorResultMappings().isEmpty();
if (this.shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = this.applyAutomaticMappings(rsw, resultMap, metaObject, (String)null) || foundValues;
}
foundValues = this.applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, (String)null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
resultObject = foundValues ? resultObject : null;
return resultObject;
} else {
return resultObject;
}
}
第3行创建一个对象,属性都为空。
第10行给这个对象属性赋值,赋值的过程就是根据org.apache.ibatis.mapping.resultMap获List propertyMappings,遍历List propertyMappings,在ResultSet里获取相应的属性,获取时使用的是TypeHandler获取的,ResultMapping可以获取到TypeHandler,然后调用Bean适当的方法设置值,setXXX是使用反射调用的。
外层代码循环处理ResultSet,对于生成的每个Bean收集到org.apache.ibatis.executor.result.DefaultResultHandler。DefaultResultHandler中的list就包含了结果集中生成的所有Bean,这个list又被外层的List包含,外层的List对应的是不同的ResultMap解析出来的Bean集合,一般配置一个ResultMap,所以外层List就一个元素,至于什么情况下配置两个ResultMap我还没有见到。
至此,使用org.apache.ibatis.session.selectOne查询的过程就已经描述完了。
org.apache.ibatis.session.SqlSession.update方法
该方法也是在SqlSession上直接执行的,不需要Mapper。这个方法执行增、删、改的sql。
该方法的执行过程与上述org.apache.ibatis.session.SqlSession.selectOne的过程基本一致,他们执行到PreparedStatement.execute基本都是一样的,只是selectOne方法后面是解析ResultSet,而update方法是获取变更的记录数。
org.apache.ibatis.session.SqlSession参数
org.apache.ibatis.session.SqlSession系列方法,传递参数的话只能传递一个值,这个值可以是:单个对象、集合类型(Collection、Array)、Map。那么框架是如何支持他们的呢?原因是:框架包含org.apache.ibatis.reflection.MetaObject类和org.apache.ibatis.reflection.wrapper包下的一些XXXWrapper完成的。
关于TypeHandler
org.apache.ibatis.type.TypeHandlerRegistry这个类里注册是很多个org.apache.ibatis.type.TypeHandler,TypeHandler的功能就是:在执行sql时把参数以适当类型设置给PreparedStatement,在解析ResultSet时把执行的列转化为对应的类型。
org.apache.ibatis.type.TypeHandlerRegistry把TypeHandler存放在一个两层的Map中,外层Map的key为Java Class,value为一个内层Map;内层Map的key为org.apache.ibatis.type.JdbcType(这是个枚举类型),value为对应的Handler。