SqlSession执行Mapper过程
- 概述
- Mapper接口的注册过程
-
- MapperRegistry
- MapperProxyFactory
- MapperProxy
- MappedStatement注册过程
-
- 1.XMLConfigBuilder#mapperElement
- 2.XMLConfigBuilder#parse
- 3.XMLConfigBuilder#configurationElement
- 4.XMLMapperBuilder#buildStatementFromContext
- 5.XMLStatementBuilder#parseStatementNode
- Mapper方法调用过程(待完善)
-
- cachedMapperMethod
- MapperMethod类
- SqlCommand类
- SqlCommand#resolveMappedStatement
- MethodSignature
- ParamNameResolve
- Mapper方法的执行
- MapperMethod#execute
- SqlSession执行Mapper过程
-
- 1.DefaultSqlSession#selectList
- 2.BaseExecutor#query
- 3.BaseExecutor#queryFromDatabase
- 4.SimpleExecutor#doQuery
- 5.SimpleExecutor#prepareStatement
- 6.PreparedStatementHandler#query
- 7.DefaultResultSetHandler#handleResultSets
概述
Mapper由两部分组成,分别为 Mapper接口 和 通过注解或者XML文件 配置的 SQL语句
将SqlSession执行Mapper过程拆解为4部分
- Mapper接口的注册过程
- MappedStatement对象 的 注册过程
- Mapper方法 的 调用过程
- SqlSession执行Mapper 的过程
Mapper接口的注册过程
Mapper接口 用于定义 执行SQL语句相关的方法,方法名一般和Mapper XML配置文件中<select|update|delete|insert>标签的id属性相同
接口的完全限定名 一般对应 Mapper XML配置文件的命名空间
Mybatis-SqlSession的创建过程中提到了DefaultSqlSession类中有Configuration和Executor成员变量
通过DefaultSqlSession#getMapper方法得到Mapper对象
实际上执行的是configuration#getMapper
而configuration#getMapper中调用的是mapperRegistry#getMapper方法
MapperRegistry
public class MapperRegistry {
// Configuration 对象引用
private final Configuration config;
// 用于注册 (Mapper接口 对应的 Class对象) 和 MapperProxyFactory对象 的对应关系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 重要!!!
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 重要!!! 创建一个MapperProxyFactory对象
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
// ...
}
MapperRegistry#getMapper方法,能够根据 Mapper接口的Class对象 获取 对应的MapperProxyFactory对象
然后就可以 使用MapperProxyFactory对象 创建 MapperProxy-动态代理对象了
MapperRegistry类有一个knownMappers属性,用于 注册(动词) (Mapper接口 对应的 Class对象) 和MapperProxyFactory对象 之间的关系
MapperRegistry#addMapper方法,用于向knownMappers属性中注册Mapper接口信息
在
addMapper
方法中,为每个(Mapper接口对应的Class对象) 创建一个 MapperProxyFactory对象,然后添加到knownMappers属性中
MyBatis框架 在应用启动时 会解析 所有的Mapper接口,然后 调用MapperRegistry对象的
addMapper
方法 将 Mapper接口信息 和 对应的MapperProxyFactory对象 注册到 MapperRegistry对象中,路径如下(后续完善时序图):
- SqlSessionFactoryBuilder#build
- new XMLConfigBuilder parser
- XMLConfigBuilder#parse
- XMLConfigBuilder#parseConfiguration
- XMLConfigBuilder#mapperElement
- Configuration#addMappers、Configuration#addMapper
- MapperRegistry.addMapper
MapperProxyFactory
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
// 重要!!!
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
// 重要!!!
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 执行上面的那个newInstance
return newInstance(mapperProxy);
}
}
MapperProxyFactory类的工厂方法newInstance()是非静态的
也就是说,使用MapperProxyFactory 创建 Mapper动态代理对象 首先需要 创建MapperProxyFactory实例(by MapperRegistry#addMapper) -> MapperProxyFactory实例是什么时候创建?
MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// ...
}
MapperProxy使用的是JDK内置的动态代理,实现了InvocationHandler接口,
invoke
方法中 为 通用的 拦截逻辑
定义方法执行拦截逻辑后,还需要调用java.lang.reflect.Proxy类的
newProxyInstance
方法 创建 代理对象(MapperProxy)
MyBatis对这一过程做了封装,使用MapperProxyFactory创建Mapper(的)动态代理对象MapperProxy,就是上面的内容
MappedStatement注册过程
MyBatis 通过 MappedStatement类 描述 Mapper的SQL配置信息
SQL配置有两种方式:一种是通过XML文件配置;另一种是通过Java注解
而Java注解的本质就是一种轻量级的配置信息
Configuration类中有一个mappedStatements属性,该属性 用于 注册 MyBatis中所有的MappedStatement对象
mappedStatements属性是一个Map对象,它的Key为Mapper SQL配置的Id
如果SQL是通过XML配置的,则Id为命名空间加上<select|update|delete|insert>标签的Id
如果SQL通过Java注解配置,则Id为Mapper接口的完全限定名(包括包名)加上方法名称
Configuration类中提供了一个
addMappedStatement
方法,用于将MappedStatement对象添加到mappedStatements属性中
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
MappedStatement对象的创建过程 必须重点关注 <mappers>标签的解析过程
<mappers>标签是通过XMLConfigBuilder类的
mapperElement
方法来解析的
1.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
// 这里
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 这里
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
如上面的代码所示,在
mapperElement
方法中,首先获取<mappers>所有子标签(<mapper>标签或<package>标签),然后根据不同的标签做不同的处理
<mappers>标签配置Mapper信息有以下几种方式:
Mapper SQL配置文件的解析需要借助XMLMapperBuilder对象
在
mapperElement
方法中,首先创建一个XMLMapperBuilder对象
然后调用XMLMapperBuilder对象的
parse
方法完成解析
2.XMLConfigBuilder#parse
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
上面的代码中,首先调用XPathParser对象的
evalNode
方法获取根节点对应的XNode对象
接着调用
configurationElement
方法对Mapper配置内容做进一步解析
3.XMLConfigBuilder#configurationElement
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
// 这里
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
configurationElement
方法中,对Mapper SQL配置文件的所有标签进行解析
关注<select|insert|update|delete>标签的解析
在上面的代码中,获取<select|insert|update|delete>标签节点对应的XNode对象后,调用XMLMapperBuilder类的
buildStatementFromContext
方法做进一步解析处理
4.XMLMapperBuilder#buildStatementFromContext
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
<select|insert|update|delete>标签的解析 需要依赖于 XMLStatementBuilder对象
XMLMapperBuilder类的
buildStatementFromContext
方法中对所有XNode对象进行遍历
然后为每个<select|insert|update|delete>标签 对应的XNode对象 创建一个 XMLStatementBuilder对象
接着调用XMLStatementBuilder对象的
parseStatementNode
方法进行解析处理
5.XMLStatementBuilder#parseStatementNode
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
XMLStatementBuilder类的parseStatementNode()方法的内容相对较多,主要做了以下几件事情:
- 获取<select|insert|delete|update>标签的所有属性信息
- 将<include>标签引用的SQL片段替换为对应的标签中定义的内容
- 获取lang属性指定的LanguageDriver,通过LanguageDriver创建SqlSource。MyBatis中的SqlSource表示一个SQL资源
- 获取KeyGenerator对象,KeyGenerator的不同实例代表不同的主键生成策略
- 所有解析工作完成后,使用MapperBuilderAssistant对象的
方法创建MappedStatement对象。创建完成后,调用Configuration对象的addMappedStatement
方法将MappedStatement对象 注册到 Configuration对象中(这一处理在MapperBuilderAssistant#addMappedStatement方法中)addMappedStatement
需要注意的是,MapperBuilderAssistant是一个辅助工具类,用于构建Mapper相关的对象,例如Cache、ParameterMap、ResultMap等
Mapper方法调用过程(待完善)
为了执行Mapper接口中定义的方法,首先需要调用SqlSession对象的getMapper()方法获取一个动态代理对象,然后通过 代理对象 调用方法即可
在MapperProxy类的
invoke
方法中,对 从Object类 继承来的方法 不做任何处理,对Mapper接口中定义的方法,调用
cachedMapperMethod
方法获取一个MapperMethod对象
cachedMapperMethod
cachedMapperMethod()方法中对MapperMethod对象做了缓存,首先从缓存中获取,如果获取不到,则创建MapperMethod对象,然后添加到缓存中,这是享元思想的应用,避免频繁创建和回收对象
MapperMethod类
在MapperMethod构造方法中创建了一个SqlCommand对象和一个MethodSignature对象:SqlCommand对象用于获取SQL语句的类型、Mapper的Id等信息;MethodSignature对象用于获取方法的签名信息,例如Mapper方法的参数名、参数注解等信息
SqlCommand类
SqlCommand构造方法中调用resolveMappedStatement()方法,根据Mapper接口的完全限定名和方法名获取对应的MappedStatement对象,然后通过MappedStatement对象获取SQL语句的类型和Mapper的Id
SqlCommand#resolveMappedStatement
首先将接口的完全限定名和方法名进行拼接,作为Mapper的Id从Configuration对象中查找对应的MappedStatement对象,如果查找不到,则判断该方法是否是从父接口中继承的,如果是,就以父接口作为参数递归调用resolveMappedStatement()方法,若找到对应的MappedStatement对象,则返回该对象,否则返回null
SqlCommand对象封装了SQL语句的类型和Mapper的Id
MethodSignature
MethodSignature构造方法中只做了3件事情:
(1)获取Mapper方法的返回值类型,具体是哪种类型,通过boolean类型的属性进行标记。例如,当返回值类型为void时,returnsVoid属性值为true,当返回值类型为List时,将returnsMap属性值设置为true。MethodSignature类中标记Mapper返回值类型的属性如下
2)记录RowBounds参数位置,用于处理后续的分页查询,同时记录ResultHandler参数位置,用于处理从数据库中检索的每一行数据。
(3)创建ParamNameResolver对象。ParamNameResolver对象用于解析Mapper方法中的参数名称及参数注解信息
ParamNameResolve
在ParamNameResolver构造方法中,对所有Mapper方法的所有参数信息进行遍历,首先判断参数中是否有@Param注解,如果包含@Param注解,就从注解中获取参数名称,如果参数中没有@Param注解,就根据MyBatis主配置文件中的useActualParamName参数确定是否获取实际方法定义的参数名称,若useActualParamName参数值为true,则使用方法定义的参数名称。解析完毕后,将参数信息保存在一个不可修改的names属性中,该属性是一个SortedMap<Integer, String>类型的对象。
到此为止,整个MapperMethod对象的创建过程已经完成
Mapper方法的执行
MapperMethod提供了一个execute()方法,用于执行SQL命令
在MapperProxy类的invoke()方法中获取MapperMethod对象后,最终会调用MapperMethod类的execute()
MapperMethod#execute
在execute()方法中,首先根据SqlCommand对象获取SQL语句的类型,然后根
据SQL语句的类型调用SqlSession对象对应的方法。例如,当SQL语句类型为INSERT时,通过SqlCommand对象获取Mapper的Id,然后调用SqlSession对象的insert()方法。MyBatis通过动态代理将Mapper方法的调用转换成通过SqlSession提供的API方法完成数据库的增删改查操作,即旧的iBatis框架调用Mapper的方式
SqlSession执行Mapper过程
MyBatis通过 动态代理 将Mapper方法的调用 转换为 调用SqlSession提供的增删改查方法
以Mapper的Id作为参数,执行数据库的增删改查操作
1.DefaultSqlSession#selectList
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
在DefaultSqlSession的
selectList
方法中,首先根据Mapper的Id从Configuration对象中获取对应的MappedStatement对象
然后以MappedStatement对象作为参数,调用Executor实例的
query
方法完成查询操作
2.BaseExecutor#query
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
在BaseExecutor类的
query
方法中,首先从MappedStatement对象中获取BoundSql对象
BoundSql类中 封装了 经过解析后的SQL语句 及 参数映射信息
然后创建CacheKey对象,该对象用于缓存的Key值
接着调用重载的
query
方法
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
在重载的query()方法中,首先从MyBatis一级缓存中获取查询结果,如果缓存中没有,则调用BaseExecutor类的
queryFromDatabase
方法从数据库中查询
3.BaseExecutor#queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
在queryFromDatabase()方法中,调用
doQuery
方法进行查询,然后将查询结果进行缓存
doQuery
是一个模板方法,由BaseExecutor子类实现
Executor有几个不同的实现,分别为BatchExecutor、SimpleExecutor和ReuseExecutor
4.SimpleExecutor#doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
在SimpleExecutor类的
doQuery
方法中,首先调用Configuration对象的
newStatementHandler
方法创建StatementHandler对象
newStatementHandler
方法返回的是RoutingStatementHandler的实例
在RoutingStatementHandler类中,会根据配置Mapper时statementType属性指定的StatementHandler类型创建对应的StatementHandler实例进行处理
例如statementType属性值为SIMPLE时,则创建SimpleStatementHandler实例
StatementHandler对象创建完毕后,接着调用SimpleExecutor类的
prepareStatement
方法创建JDBC中的Statement对象,然后为Statement对象设置参数操作
Statement对象初始化工作完成后,再调用StatementHandler的
query
方法执行查询操作
5.SimpleExecutor#prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
在SimpleExecutor类的
prepareStatement
方法中,首先获取JDBC中的Connection对象
然后调用StatementHandler对象的
prepare
方法创建Statement对象
接着调用StatementHandler对象的
parameterize
方法(parameterize()方法中会使用ParameterHandler为Statement对象设置参数)
MyBatis的StatementHandler接口有几个不同的实现类,分别为SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler
MyBatis默认情况下会使用PreparedStatementHandler与数据库交互
6.PreparedStatementHandler#query
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
在PreparedStatementHandler的
query
方法中,首先调用PreparedStatement对象的
execute
方法执行SQL语句
然后调用ResultSetHandler的
handleResultSets
方法处理结果集
ResultSetHandler只有一个默认的实现,即DefaultResultSetHandler类
7.DefaultResultSetHandler#handleResultSets
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
DefaultResultSetHandler类的handleResultSets()方法具体逻辑如下:
- 首先从Statement对象中获取ResultSet对象,然后 将ResultSet 包装为 ResultSetWrapper对象,通过ResultSetWrapper对象 能够更方便地 获取 数据库字段名称 以及 字段对应的TypeHandler信息
- 获取Mapper SQL配置中 通过resultMap属性 指定的 ResultMap信息,一条SQL Mapper配置一般只对应一个ResultMap
- 调用
方法对ResultSetWrapper对象进行处理,将结果集转换为Java实体对象,然后将 生成的实体对象 存放在 multipleResults列表中handleResultSet
- 调用
方法对multipleResults进行处理,如果只有一个结果集,就返回结果集中的元素,否则返回多个结果集collapseSingleResultList