Mybatis源码分析-Mybatis是如何执行sql的

3 篇文章 0 订阅
2 篇文章 0 订阅

Mybatis源码分析-Mybatis是如何执行sql的

使用mybatis作为dao层框架是目前较主流的一种方案,与spring框架整合下的实践一般是先编写mapper接口,再编写mapper的xml文件,最后在service层中调用mapper接口进行数据库层面操作。举个系统岗位实例的curd做例子:

  • mapper接口
public interface SysPostMapper
{
    /**
     * 查询岗位数据集合
     * 
     * @param post 岗位信息
     * @return 岗位数据集合
     */
    public List<SysPost> selectPostList(SysPost post);

    /**
     * 查询所有岗位
     * 
     * @return 岗位列表
     */
    public List<SysPost> selectPostAll();

}
  • mapper的xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 命名空间使用mapper接口全路径  -->
<mapper namespace="com.ruoyi.system.mapper.SysPostMapper">

   <resultMap type="SysPost" id="SysPostResult">
      <id     property="postId"        column="post_id"       />
      <result property="postCode"      column="post_code"     />
      <result property="postName"      column="post_name"     />
      <result property="postSort"      column="post_sort"     />
      <result property="status"        column="status"        />
      <result property="createBy"      column="create_by"     />
      <result property="createTime"    column="create_time"   />
      <result property="updateBy"      column="update_by"     />
      <result property="updateTime"    column="update_time"   />
      <result property="remark"        column="remark"        />
   </resultMap>
   <cache></cache>
   <sql id="selectPostVo">
        select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark 
      from sys_post
    </sql>
   <!-- namespace + id 将组成mappedStatement的唯一标识符  -->
   <select id="selectPostList" parameterType="SysPost" resultMap="SysPostResult">
       <include refid="selectPostVo"/>
      <where>
         <if test="postCode != null and postCode != ''">
            AND post_code like concat('%', #{postCode}, '%')
         </if>
         <if test="status != null and status != ''">
            AND status = #{status}
         </if>
         <if test="postName != null and postName != ''">
            AND post_name like concat('%', #{postName}, '%')
         </if>
      </where>
   </select>
   
   <select id="selectPostAll" resultMap="SysPostResult">
      <include refid="selectPostVo"/>
   </select>

</mapper> 
  • service层调用
@Service
public class SysPostServiceImpl implements ISysPostService
{
    @Autowired
    private SysPostMapper postMapper;

    @Autowired
    private SysUserPostMapper userPostMapper;

    /**
     * 查询岗位信息集合
     * 
     * @param post 岗位信息
     * @return 岗位信息集合
     */
    @Override
    public List<SysPost> selectPostList(SysPost post)
    {
        return postMapper.selectPostList(post);
    }

    /**
     * 查询所有岗位
     * 
     * @return 岗位列表
     */
    @Override
    public List<SysPost> selectPostAll()
    {
        return postMapper.selectPostAll();
    }
}

举例完毕,本文要探究的是为什么调用mapper接口就可以操作数据库?

1 mapper接口不是接口,是动态代理类

接口自然是无法直接调用,真正在调用的其实是增强了该接口的动态代理类。mybatis使用的动态代理是基于jdk自带的那种,即基于接口的。下面举个例子说明下:

  • mapper接口
public interface StudentMapper {
    void study();
}
  • 增强器(即实现了InvocationHandler)
public class MapperProxy<T> implements InvocationHandler {

    private final Class<T> mapperInterface;

    public MapperProxy(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("虽然是个接口,没有任何实现类,我也能直接执行!");
		System.out.println("我还能知道你正在调用的方法:" + method.getName());
        return null;
    }
}
  • demo演示
public class Demo {
    public static void main(String[] args) {
        MapperProxy<StudentMapper> mapperProxy = new MapperProxy<>(StudentMapper.class);
        StudentMapper studentMapper = (StudentMapper) Proxy.newProxyInstance(mapperProxy.getClass().getClassLoader(), new Class[]{StudentMapper.class}, mapperProxy);
        studentMapper.study();
    }
}

studentMapper是个接口,也没有任何实现类,但也可直接调用,因为实际调用的是动态代理类,其具体逻辑定义在MapperProxyinvoke方法中。例子虽简单,但mybatis的做法其实也是类似的,下面一起探究。

首先从DefaultSqlSession类的getMapper方法切入,其实与spring整合的场景下一般感知不到这个步骤,因为mapper的动态代理类已经交给spring容器管理了。

// org.apache.ibatis.session.defaults.DefaultSqlSession
@Override
public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}
// org.apache.ibatis.session.Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}
// org.apache.ibatis.binding.MapperRegistry
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);
  }
}
// org.apache.ibatis.binding.MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
    // MapperProxy实现了InvocationHandler接口
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
  protected T newInstance(MapperProxy<T> mapperProxy) {
      // 基于jdk自动的动态代理生成
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

看看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);
    }
  }
    // ...................省略......................
}

由上面的源码分析可知,mapper接口执行的实际逻辑定义在MapperProxy的invoke方法中

2 mapper接口执行查询类sql的过程

2.1 mapper接口的执行入口

上面说过mapper接口是个动态代理类,分析过增强的逻辑即可知道真正执行逻辑封装在类MapperMehod中,故从此类切入

// org.apache.ibatis.binding.MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
    // command这类信息的封装在前面的节点已处理,大体可理解为从mapper的xml文件解析所得
  switch (command.getType()) {
    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;
    }
          // 以select类型的sql为例子
    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;
	// ...............省略..............
}
    
   private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
       // 判断是否有分页信息数据
       // 实际使用的是SqlSessionTemplate处理
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  } 
// org.mybatis.spring.SqlSessionTemplateorg.mybatis.spring.SqlSessionTemplate
// 与spring整合的环境下使用这个组件,其被SqlSessionInterceptor增强
@Override
public <E> List<E> selectList(String statement, Object parameter) {
  return this.sqlSessionProxy.selectList(statement, parameter);
}
// -----增强的逻辑----------
// org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //在此获取正在的sqlSession,后续详细分析
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
          // 调用目标方法
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

2.2 获取SqlSession

说明下获取sqlSession的过程

// org.mybatis.spring.SqlSessionUtils
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

  notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
  notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 从事务同步管理器中观察是否已经有本线程之前放过的sqlSession对象,有则直接返回
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

  SqlSession session = sessionHolder(executorType, holder);
  if (session != null) {
    return session;
  }

  LOGGER.debug(() -> "Creating a new SqlSession");
    // 没有则新获取一个session对象
  session = sessionFactory.openSession(executorType);
	// 把session绑定到线程上,当然这需要看是否开启配置 TransactionSynchronization
  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}

TransactionSynchronizationManager这个对象是spring-tx包下的,与spring的事务相关,可存放很多线程相关的变量,值得关注,后期将mybatis和事务结合起来的时候再重点分析。

接下来重点分析下openSession的过程

//org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
// 其实大部分情况都是调用openSessionFromDataSource此方法获取session
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    final Environment environment = configuration.getEnvironment();
      // 与spring结合的话则是SpringManagedTransactionFactory,具体可看mybatisAutoConfiguration的配置
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // SpringManagedTransaction
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // CachingExecutor 代理对象SimpleExecutor 封装变量transaction configuration
    final Executor executor = configuration.newExecutor(tx, execType);
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

session中必然要有数据库的信息,transaction封装了dataSourceexecutor中封装Transaction,最终在DefaultSqlSession中封装executor,链式调用,最终就有此信息

看看获取Executor的过程

//org.apache.ibatis.session.Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
    // 根据不同类型生成,一般都是 Simple那种,然后再用Caching套上
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
    // 值得注意,executor是会被增强的,此为插件机制的原理
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}
2.2.1 值得关注的插件增强

用过分页插件的都知道其原理是增强了executor的实现类,增强的插入点就在这里:

//org.apache.ibatis.plugin.InterceptorChain
//这个chain啊,其实myatis的configuration中addInterceptor的时候会往里头加插件
public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
      // 返回的target是个代理对象,经过遍历后就会,,,一层套一层.....
    target = interceptor.plugin(target);
  }
  return target;
}
//org.apache.ibatis.plugin.Interceptor
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
// org.apache.ibatis.plugin.Plugin
// 最核心的逻辑在这里
// plugin类实现了InvocationHandler接口,很显然有要进行动态代理增强了
// 这里的target一般是simpleExecutor
public static Object wrap(Object target, Interceptor interceptor) {
    // 一般inteceptor会在类开头写下这类注解元信息,可参考PageInterceptor
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    // 获取type实现的接口中在signatureMap中定义的,说直白点就是判断是否要拦截这个接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

//-----------------------------------分隔符-------------------------------------------
// plugin既然实现了InvocationHandler接口,那就看看他的增强逻辑吧
//org.apache.ibatis.plugin.Plugin
// 成员变量
  private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }
// 增强的逻辑
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 只有定义的方法才拦截
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

总结:executor是有可能被插件增强过的。

上面这一大段都是如何获取sqlSession的逻辑,为啥这么复杂呢?因为与spring整合后有事务这种概念,同个事务使用相同的sqlSession,引入了spring-tx中的组件TransactionSynchronizationManager用于获取同个线程中先前存过的sqlSession(如果存在)。梳理下来有这么几点:

  • 获取组件:executor transaction sqlsession
  • 组件的依赖,executor需要transactiontransaction中封了datasource的信息,sqlSession需要executor
  • 其他的点:
    • 如果TransactionSynchronizationManagerSqlSession支持直接返回,没有得新获取
    • executor是可以被plugin增强的

2.3 sqlSession的查询逻辑

有了sqlSession总算可以查数据了,下面开始分析!

回到逻辑:org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke

通过反射的方式调用类org.apache.ibatis.session.defaults.DefaultSqlSessionselectList方法

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    // wrapCollection的逻辑比较简单,如果parameter是collection或array类型的则套一层返回ParamMap对象来返回
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

经过多个重载方法后最终进入executor.query(ms, wrapCollection(parameter), rowBounds, handler);

如果statement有使用cache则会先从cache取,即配置二级缓存,不过目前没遇到配cache的情况,故简化处理,具体逻辑在cachingExecutor的deltegate成员,即simpleExecutor

// org.apache.ibatis.executor.SimpleExecutor
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;
}
// 从数据库中取
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;
  }

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,后面细讲
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
        // 预编译statement对象
      stmt = prepareStatement(handler, ms.getStatementLog());
        // 查询获取结果
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

总结下获取数据的过程:一般都是通过simpleExecutor此组件获取数据,先从一级缓存取,没有则从数据库取,然后放入缓存中;

2.4 StatemnetHandler组件真干活

获取数据到封装数据有个很重要的组件StatementHandler,接下来重点说明:

2.4.1 获取StatemnetHandler过程
// org.apache.ibatis.session.Configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 实际上的类型是RoutingStatementHandler,根据ms.getStatementType让其包裹一个delegate做真正的活,一般是PreparedStatementHandler
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 一样的,statementHandler也可以被plugin增强,但很少这么搞,比如PageInterceptor就不增强statementHandler接口
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

2.4.2 基于StatementHandler获取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;
}
// org.apache.ibatis.executor.statement.PreparedStatementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
        // 一般情况都是直接 connection.prepareStatement(sql);
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

//org.apache.ibatis.scripting.defaults.DefaultParameterHandler
//从handler.parameterize(stmt); 最终将到此处的逻辑
public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 这东西和预编译语句中的?一一对应
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
            // 输入属性进此逻辑
          Object value;
            // 获取属性名
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
              //这里的parameterObject就是指xxxxMapper中接口被调用时上送的参数
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) 		   {// 是否有对应的类型处理器
            value = parameterObject;
          } else {
              //这种情况指的是上送的若是个对象,一般都没有类型处理器嘛
              // 此时就通过属性名获取对应对象的getxxx的返回值
            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 {
              // 设置参数,最终底层执行jdbc的操作,如ps.setLong(i,value)
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

总结下获取statement的过程:

  • 获取connection
  • 通过connection和sql获取prepareStatement
  • 将参数填充到statement中,参数已经在mappedStatemntbounedSqlParameterMappings中封好,依次遍历封装进去
2.4.3 使用StatementHandler获取数据
// org.apache.ibatis.executor.statement.PreparedStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  return resultSetHandler.handleResultSets(ps);
}
// -----------------分割线--------------------
// org.apache.ibatis.executor.resultset.DefaultResultSetHandler
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;
    // 比较简单,调stmt.getResultSet(),然后包一层到ResultSetWrapper中
    // 当然如果用到了druid这类的连接池会比较复杂,但终究用的是resultSet的接口标准
    ResultSetWrapper rsw = getFirstResultSet(stmt);
	// xml中的resultMap有值则封装到这里
    // 一般只会有一个,不考虑多resultMap的情况
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
        //处理结果,结束后将结果封装到multipleResults对象中,具体分析在后面
      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);
  }

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
        // 简化分析,不考虑父mapping的情况
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
            // 使用默认的,DefaultResultHandler
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
            // 处理完将defaultResultHandler封装的结果搬到multipleResults对象中
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
        // 只分析simple的ResultMap,不考虑嵌套那种的
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    skipRows(resultSet, rowBounds);
    // 此循环即遍历resultSet的入口,最终结果封装到DefaultResultHandler组件中
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
        // 不考虑这种
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
        // 将resultSet的一个结果封装到rowValue的java对象中,具体过程后面细说
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
        // 逻辑比较简单,将rowValue放入DefaultResultHandler的list变量中,为啥套了好几层就搞不懂
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
    }
  }

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    // 根据resultMap中的type创建返回的java对象,一般通过反射方式
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      if (shouldApplyAutomaticMappings(resultMap, false)) {
          // 一对没映射到的属性应用属性自动映射方式填充对象的属性值
          // 填充的方式后面详细展开
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
        // 映射属性填充到rowValue中
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
        // 可以配置是返回新对象还是返回null
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    // 返回值
    return rowValue;
  }

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    this.useConstructorMappings = false; // reset previous mapping result
    final List<Class<?>> constructorArgTypes = new ArrayList<>();
    final List<Object> constructorArgs = new ArrayList<>();
    // 获取返回对象,下面细说
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      for (ResultMapping propertyMapping : propertyMappings) {
        // issue gcode #109 && issue #149
          // 遍历resultMapping,如果存在懒加载属性,则通过动态代理方式增强返回对象
          // 不细展开,应该挺复杂的...
        if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) 		{
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
          break;
        }
      }
    }
    this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
    return resultObject;
  }

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
      throws SQLException {
    final Class<?> resultType = resultMap.getType();
    final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
        // 存在resultType对应的typeHandler则最终会调用其getResult方法获取值返回
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    } else if (!constructorMappings.isEmpty()) {
        // 目前没见过用这种的
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
        // 一般是这种,通过反射创建返回对象
      return objectFactory.create(resultType);
    } else if (shouldApplyAutomaticMappings(resultMap, false)) {
      return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
    }
    throw new ExecutorException("Do not know how to create an instance of " + resultType);
  }


// -------------------分隔符-------------------
//--------------自动装配属性的逻辑-----------------
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    // 创建未被resultMap映射列的信息,逻辑也简单,遍历column找出没在resultmap中定义过的
    List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
          // 对不存在resultMap的属性用typeHandler获取值
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }
// 填充映射的属性,即resultMap中有定义的
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      if (propertyMapping.getNestedResultMapId() != null) {
        // the user added a column attribute to a nested result map, ignore it
        column = null;
      }
        // 一般不会进入此逻辑
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // issue #541 make property optional
        final String property = propertyMapping.getProperty();
        if (property == null) {
          continue;
        } else if (value == DEFERRED) {
          foundValues = true;
          continue;
        }
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          // gcode issue #377, call setter on nulls (value is not 'found')
            // 此逻辑较简单,metaObject中封装了目标返回对象rowValue,通过property名称寻找setProperty方法,通过方式方式将value值填充到rowValue中
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

上面分析了使用preparedStatementHandler获取对象的具体过程,总结成下面几点:

  • 调用statement.execute,其实就是进行查库
  • 使用DefaultResultSetHandler的handleResultSets方法处理数据,其实就是遍历resultset,通过反射方式创建对象,设置对象值
  • 理一下几个组件:metaObject rowValue DefaultResultHandler的list变量 multipleResults,先创建对象作为rowValue,把rowValue封到metaObject中,根据resultSet封属性的时候操作的是metaObject,那么其实也封到了rowValue中,rowValue后面会封到DefaultResultHandler的list变量,从DefaultResultHandler的list变量赋值到multipleResults后返回

到此,获取数据的整个过程结束,后续操作一般是善后类的,如关闭sqlSession之类

3 mapper接口执行更新类sql的过程

前面介绍了查询语句的逻辑,下面介绍下更新语句的逻辑,有了上面的分析过程,看update就轻松很多。

还是从MapperProxy切入

// org.apache.ibatis.binding.MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
   // .................省略
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
        // 最终调用sqlSessionTemplate进行update操作
      result = rowCountResult(sqlSession.update(command.getName(), param));
      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;
}
    // -----------------------分割--------------
    // org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
    // sqlSession此对象被SqlSessionInterceptor增强,故先入此逻辑,基本都是这种操作
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

mapperMethod最终也是调用DefaultSqlSessionupdate方法,具体分析下此方法

// org.apache.ibatis.session.defaults.DefaultSqlSession
public int update(String statement, Object parameter) {
  try {
    dirty = true;
    MappedStatement ms = configuration.getMappedStatement(statement);
      // caChingExecutor,但其实可能被plugin增强过
    return executor.update(ms, wrapCollection(parameter));
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

// org.apache.ibatis.executor.SimpleExecutor
// cachingExecutor最终使用的也是SimpleExecutor组件
public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
    // 清空一级缓存
  clearLocalCache();
  return doUpdate(ms, parameter);
}

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
        // 和查询操作一样,也是使用statementHandler,具体使用的是RoutingStatementHandler
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

获取prepareStatement的过程和查询是一样的,三步走:1获取连接connection,2基于connection生成prepareStatement,3使用statementHandler填充prepareStatement中的参数

下面具体分析statementHandlerupdate逻辑

// org.apache.ibatis.executor.statement.PreparedStatementHandler
public int update(Statement statement) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
    // 执行jdbc的查询
  ps.execute();
  int rows = ps.getUpdateCount();
    // 获取当初mapper中上送的参数
  Object parameterObject = boundSql.getParameterObject();
    // key生成器,update操作一般没配,则是 NoKeyGenerator,无逻辑的类
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
  return rows;
}

更新操作的逻辑很简单,没有查询那么复杂。关于key生成器的使用虽然update操作没用到,但是insert操作时经常使用,具体表现就是如果主键是自增的,则插入完成后可将自增生成的主键自动设置到对象中,下面具体分析下其逻辑

3.1 关于KeyGenerator

keyGenerator是一个接口,mybatis提供了几个实现类:

  • Jdbc3KeyGenerator 自增主键那一类使用这种
  • SelectKeyGenerator oracle那类需要指定selectKey的使用这种
  • NoKeyGenerator 空操作

分析下Jdbc3KeyGenerator 的逻辑

// org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
  processBatch(ms, stmt, parameter);
}
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
    // ms里已经封好了主键属性
    final String[] keyProperties = ms.getKeyProperties();
    if (keyProperties == null || keyProperties.length == 0) {
      return;
    }
    try (ResultSet rs = stmt.getGeneratedKeys()) {
        // 获取rs的元信息
      final ResultSetMetaData rsmd = rs.getMetaData();
      final Configuration configuration = ms.getConfiguration();
      if (rsmd.getColumnCount() < keyProperties.length) {
        // Error?
      } else {
          // 映射键,逻辑较复杂,不深究了,打住
        assignKeys(configuration, rs, rsmd, keyProperties, parameter);
      }
    } catch (Exception e) {
      throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
    }
  }

4 sql执行过程总结

画图总结

参考自 《一本小小的MyBatis源码分析书》

5 最后

本文从源码角度分析了mybatis执行sql的过程,可能有不严谨和理解不正确的地方,在以后有更多使用经验后不断修正。

参考文章:

MyBatis 源码分析系列文章合集

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值