日常开发使用mybatis进行CURD操作十分简便,我们只需要在Mapper接口定义好方法,然后在mapper.xml中写好SQL语句,就能在业务代码中使用了。这简单到令人发指步骤,让我们麻痹到以为这个过程是非常简单的,时间久了脑子都笨了。今天,接着上一篇文章中的例子,结合源码学习一下mybatis中一条SQL的执行过程,了解下背后发生的故事。
从Demo开始
还是之前的例子,代码如下。
示例代码
<!--CompanyDAO.xml-->
<select id="selectById" resultMap="baseResultMap" >
select
<include refid="BaseColumns"></include>
from company
where id= #{id}
</select>
CompanyDAO.xml,按照id查询数据表company的记录,id为主键,selectById为statementId。
public interface CompanyDao {
/**
* 根据id查询CompanyDO
*
* @param id 主键id
* @return 查询结果
*/
CompanyDO selectById(@Param("id") Integer id);
}
CompanyDao.java,Mapper接口,仅包含一个方法selectById,按照mybatis要求,与mapper的xml配置文件一一对应。
public class Program {
public static void main(String[] args) throws IOException {
// mybatis配置文件
String path = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(path);
// 获取 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建 SqlSession
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
//获取Mapper:CompanyDao
CompanyDao dao = sqlSession.getMapper(CompanyDao.class);
//调用selectById方法
CompanyDO companyDO = dao.selectById(1);
System.out.println(companyDO);
}
}
}
Program.java业务代码,调用逻辑比较简单,再啰嗦一下:
-
通过mybatis-config.xml创建SqlSessionFactory;
-
使用SqlSessionFactory创建并打开SqlSession;
-
通过sqlSession获取CompanyDao的Mapper实例对象;
-
调用selectById方法获取查询结果;
示例分析
由以上示例代码可知,CompanyDao是一个接口,我们并没有编写它的实现类,那为啥可以创建它的对象呢?我们现在得到的到底是哪个类的实例?
打个断点!下面调试界面截图,我们发现dao是org.apache.ibatis.binding.MapperProxy的实例。
熟悉动态代理的同学,看一眼就知道MapperProxy是一个代理类。我们就从这里入手,顺藤摸瓜。
流程分析
Mapper对象怎么来的?
MapperProxy对象是通过SqlSession#getMapper方法创建的,这里的sqlSession是DefaultSqlSession的实例,跟着源码走一下。
//org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
//这里调用了configuration的方法,继续走
return configuration.<T>getMapper(type, this);
}
//org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//所有的Mapper都注册在Configuration#mapperRegistry(不过是存储的MapperProxyFactory),所以是通过它来获取Mapper对象
//下一步进入MapperRegistry#getMapper
return mapperRegistry.getMapper(type, sqlSession);
}
//org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//通过type从map中获取MapperProxyFactory实例
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
//如果mapperProxyFactory,则抛出异常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//通过mapperProxyFactory的newInstance创建实例,继续走
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
public T newInstance(SqlSession sqlSession) {
//先创建了MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
//调用了newInstance方法
return newInstance(mapperProxy);
}
//org.apache.ibatis.binding.MapperProxyFactory#newInstance
protected T newInstance(MapperProxy<T> mapperProxy) {
//到这里就揭开了代理对象创建的面纱
//使用Proxy创建了代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
为了方便梳理,上面把Mapper创建过程的代码放在一起了,我们可以发现整个过程是通过Configuration串在一起的(Configuration是mybatis运行时的基础)。简单描述一下过程:
-
入口在DefaultSqlSession#getMapper,整个方式只是一个中继,需要通过Configuration#getMapper来创建;
-
在解析mybatis配置时,所有的Mapper注册信息由Configuration#mapperRegistry存储,但是存储的其实是一个与Mapper类型相关的MapperProxyFactory,既然是Factory,那就要生产点啥了。
-
MapperProxyFactory创建了MapperProxy对象,然后通过Proxy创建了代理对象,由MapperProxy伪装了一个Mapper实例。
所以,Mapper对象(示例中的dao)其实一个“披着Mapper皮的MapperProxy对象”。上述过程虽然跳转的类比较多,一层一层走下来最主要的就是Java动态代理相关的内容。通过下面的时序图再来看下Mapper对象的创建过程吧。
代理对象invoke方法执行
创建Mapper对象后,接下来调用Mapper方法,从下面的代码开始:
CompanyDO companyDO = dao.selectById(1);
dao为MapperProxy对象,当调用selectById时,会先调用org.apache.ibatis.binding.MapperProxy#invoke方法(这里可以看下Java动态代理相关的文章,保存出代理类的动态实现,有机会写篇文章)。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断是否为Object类
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
//判断是否为默认方法
else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//走到这里说明是被代理接口的方法,获取或设置MapperMethod缓存,得到MapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
//方法执行
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
//如果缓存中不存在method,就创建一个对象添加到缓存。
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
到这里,我们可以知道方法invoke最终执行的是org.apache.ibatis.binding.MapperMethod#execute,接着看MapperMethod这个类。
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
//sql命令,包含mapper接口、mapper方法和configuration
this.command = new SqlCommand(config, mapperInterface, method);
//方法签名
this.method = new MethodSignature(config, mapperInterface, method);
}
}
MapperMethod构造方法内步创建了SqlCommand和MethodSignature,这两个类都是MapperMethod的内部类。等下看org.apache.ibatis.binding.MapperMethod#execute时会发现,它的执行与这两个类有很大关系,我们先来看下这两个做了什么。
public static class SqlCommand {
private final String name;
//sql命令类型枚举:UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
private final SqlCommandType type;
//构造方法:主要逻辑
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
//获取方法名称:示例中就是:selectById
final String methodName = method.getName();
//方法所在类的全路径限定名称:com.raysonxin.dao.CompanyDao
final Class<?> declaringClass = method.getDeclaringClass();
//解析获取MappedStatement:看下面方法的注释。
//这样就可以拿到MappedStatement对象了
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
//获取id
name = ms.getId();
//获取命令类型
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
//解析获取MappedStatement
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
//拼接获取statement,示例中是:com.raysonxin.dao.CompanyDao.selectById
//这其实就是Configuration中MappedStatement注册的key
String statementId = mapperInterface.getName() + "." + methodName;
//判断configuration是否存在这个statementId
if (configuration.hasStatement(statementId)) {
//如果存在,获取并返回
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
//这里是从父接口中获取的逻辑
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
SqlCommand就是根据接口和方法的签名信息,从Configuration中获取对应的MappedStatement,MappedStatement是在解析mybatis-config.xml是添加的,大家可以翻阅Configuration解析代码,或者查看上一篇文章《Mybatis源码之Configuration》。SqlCommand创建后,我们就可以清楚知道了其对应的MappedStatement,也就知道了它是查询、更新、删除还是添加这样的命令类型。
public static class MethodSignature {
//返回结果是否为多个值,即列表
private final boolean returnsMany;
//返回结果是否为Map类型
private final boolean returnsMap;
//返回结果是否为void
private final boolean returnsVoid;
//返回结果是否为游标
private final boolean returnsCursor;
//返回结果是否为Optioanl类型
private final boolean returnsOptional;
//返回值类型
private final Class<?> returnType;
private final String mapKey;
//
private final Integer resultHandlerIndex;
//查询数据范围参数索引
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
}
MethodSignature是通过反射获取其方法描述信息,也就是签名,代码太多,仅通过其字段和构造方法看下吧。MethodSignature主要的逻辑是获取其返回值类型,比如单个、多个、Map等。
对SqlCommand和MethodSignature两个内部类有一定的认识后,我们继续看下MapperMethod#execute方法。execute方法会根据SqlCommandType处理不同的命令:
-
INSERT、UPDATE、DELETE的处理方式应该说是基本相同,参数转换后调用对应的SqlSession方法去执行;
-
对于SELECT命令,需要根据返回值特点,转换参数、处理查询范围等,然后调用SqlSession不同的查询方法;
public class MapperMethod {
//...
//方法执行逻辑
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断命令类型:间接从MappedStatement获取的
switch (command.getType()) {
//insert数据
//INSERT、UPDATE、DELETE的处理方式一毛一样的;
//1、参数处理:把参数列表转为Map结构,同时会复制一份参数按照param0、param1这样put到Map
//2、调用SqlSession对应的方法执行命令,command.getName()就是我们示例中的com.raysonxin.dao.CompanyDao.selectById
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
//更新数据
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
//删除数据
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
//查询命令:会根据返回值的类型调用不同的处理方法,进而路由到SqlSession不同的查询方法,在此之前需要做一些准备工作:
//selectOne、selectList、selectCursor等
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
//...
}
MapperMethod#execute最终调用了SqlSession接口的方法,在我们的示例中会调用SqlSession#selectOne。
走到这里是不是有点懵了,怎么调来调去,绕了一圈又回到SqlSession了?为啥不直接调用SqlSession接口呢?这里插一句:直接使用SqlSession确实是可以,而且也是早起Mybatis所支持的方式;但是它不符合面向对象语言的概念和面向接口编程的编程习惯。由于面向接口的编程是面向对象的大趋势,MyBatis 为了适应这一趋势,增加了第二种使用MyBatis 支持接口(Interface)调用方式,即Mapper接口。
这一段过程的主要工作就是:采用动态代理方法,根据我们所调用的接口和方法,结合Configuration所加载的配置信息,动态的把请求路由到SqlSession对应的接口上来。
Sql命令执行
绕了一圈回到SqlSession后,才算是真正开始sql命令语句的执行。这个过程也是很复杂的,需要依次经过SqlSession、Executor、StatementHandler、ParamterHandler、ResultSetHandler等,流程较长,不能面面俱到,我们随着代码走一遍流程,然后通过后续的文章逐个补齐细节。先看个大体的流程图:
SqlSession
按照示例,DefaultSqlSession为具体实现,从DefaultSqlSession#selectOne()开始。
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
//调用selectList方法,获取返回结果list
List<T> list = this.selectList(statement, parameter);
//如果结果刚好是1个,返回;若为多个,抛出异常;若为空,返回null;
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
//中转一下,RowBounds给了默认值,不设限。
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//从configuration获取MappedStatement,
//示例中statement=com.raysonxin.dao.CompanyDao.selectById
MappedStatement ms = configuration.getMappedStatement(statement);
//通过执行器Executor执行查询
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
根据DefaultSqlSession代码走下来,从selectOne走到selectList,在selectList中调用了Executor#query方法。根据Executor的原理,当前executor为CachingExecutor实例,并且SimpleExecutor为其代理执行器(如下图),所以接下来进入Executor执行器的流程。
Executor
CachingExecutor是Executor的缓存设计,若缓存中存在所要查询的内容会直接返回,否则会交给其代理执行器通过数据库进行查询。从CachingExecutor#query(MappedStatement, Object, RowBounds, ResultHandler)开始:
//org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);
//获取缓存key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
//调用重载方法
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//查询缓存
Cache cache = ms.getCache();
//缓存存在
if (cache != null) {
//如果需要刷新缓存
flushCacheIfRequired(ms);
//如果启用缓存
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
//数据为空还是要调用委托执行器的query
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//缓存不存在时,调用委托执行器的query
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
我们看到如果未启用或者未命中缓存的情况,CachingExecutor会通过其委托执行器继续查询,也就是SimpleExecutor#query。SimpleExecutor继承自BaseExecutor,在BaseExecutor也同样具有缓存的处理逻辑,我们暂时跳过,仅贴出主要代码:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//省略。。。
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--;
}
//....
}
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 {
//执行doQuery,这个是抽象方法,具体实现在SimpleExecutor
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;
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//获取configuration
Configuration configuration = ms.getConfiguration();
//创建StatementHandler,默认为PreparedStatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//创建并初始化statement
stmt = prepareStatement(handler, ms.getStatementLog());
//由StatementHandler执行查询,使用ResultSetHandler处理查询结果
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
//创建Statement,BaseStatementHandler#prepare
stmt = handler.prepare(connection, transaction.getTimeout());
//设置参数,使用ParameterHandler
handler.parameterize(stmt);
return stmt;
}
Executor过程的执行就到这里了,CachingExecutor->BaseExecutor->SimpleExecutor,并且完成了StatementHandler执行的准备工作。CachingExecutor充当了缓存一层,当未命中缓存时会执行数据库查询,此时交给BaseExecutor及其子类执行。我们通过doQuery方法看到了StatementHandler创建、参数设置、执行与关闭整个生命周期,可以说Executor是StatementHandler的调度者。
从SimpleExecutor#doQuery、SimpleExecutor#prepareStatement方法看,可以把StatementHandler的工作分为几个过程:创建Statement对象、设置Statement参数、执行Statement查询并返回结果、关闭Statement对象。其中“设置Statement参数”需要使用ParameterHandler进行参数设置、“执行Statement查询并返回结果”需要使用ResultSetHandler处理查询结果。接下来重点看下参数设置、查询、结果处理三个过程。
ParameterHandler
prepareStatement方法中,调用了StatementHandler#parameterize进行参数设置,我们看下PreparedStatementHandler的实现。
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
这里的parameterHandler是ParameterHandler实例。在mybatis中,ParameterHandler仅有一个默认实现,即:DefaultParameterHandler。所以,直接看DefaultParameterHandler#setParameters方法:
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//获取参数映射列表,它来自SqlSource的解析过程,目前了解即可。
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
//遍历参数映射
for (int i = 0; i < parameterMappings.size(); i++) {
//获取当前参数映射对象
ParameterMapping parameterMapping = parameterMappings.get(i);
//如果参数模式不是OUT,即IN、INOUT时,继续执行
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
//获取参数名称:示例中为:id
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
}
//参数值为空,
else if (parameterObject == null) {
value = null;
}
//类型处理器是否存在当前参数类型。如果只有一个参数,这里一般会命中。
//我们编写sql语句时,没有标明入参类型,mybatis会把参数类型解析为Object
else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
}
//多个参数时,会走到这里。
else {
//转为MetaObject类似于一个map
MetaObject metaObject = configuration.newMetaObject(parameterObject);
//获取当前参数对应的值
value = metaObject.getValue(propertyName);
}
//获取类型处理器
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//通过类型处理器设置Statement参数。
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
setParameters设置参数的过程比较简单:获取参数映射列表后,依次遍历参数映射对象,逐个参数类型获取类型处理器,然后使用类型处理器设置Statement参数。当然,这里面会设置到类型处理器的查询中转操作(UnKnown->Integer),大家可以调试看下过程。
StatementHandler
参数设置完成后,就到了执行操作了。示例中将会走到PreparedStatementHandler#query,如下:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//转为PreparedStatement类型
PreparedStatement ps = (PreparedStatement) statement;
//执行
ps.execute();
//处理查询结果
return resultSetHandler.handleResultSets(ps);
}
呃,这个太简洁了。把statement类型转为PreparedStatement,执行execute,处理查询结果并返回。Statement相关的内容属于传统JDBC范畴了,大家比较熟悉,不再赘述。
ResultSetHandler
这应该是查询过程中的最后一步了!由PreparedStatementHandler#query方法可知,执行java.sql.PreparedStatement#execute后,mybatis使用ResultSetHandler#handleResultSets处理查询结果。mybatis对ResultSetHandler也仅有一个默认实现DefaultResultSetHandler,直接看代码吧:
@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();
//如果rsw为空,或者resultMapCount<1,会抛出异常
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);
}
总结一下这个阶段的处理过程,看图吧,一图胜千言!
总结
本文从一个简答的Mapper查询方法开始,详细分析了其执行过程。按照我的个人理解,把执行过程分为了三个阶段:
-
Mapper接口实例的动态实现阶段:Mybatis基于动态代理,动态实现Mapper接口,其实一个“披着Mapper皮的MapperProxy对象”;
-
Mapper接口命令解析阶段:这个过程中基于接口、方法签名,结合反射技术、Configuration配置信息,确认了对应的SqlSession方法入口。
-
SQL命令执行阶段:从SqlSession入口开始,依次经过Executor、StatementHandler、ParameterHandler、ResultSetHandler四大组件的层层处理,最终完成查询过程。
好了,文章到这里就结束了!由于本人水平有限,如有错误、不实之处,欢迎大家批评指正!
原文始发于微信公众号(码路印记):Mybatis源码之SQL执行过程
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/23792.html