Mybatis3 深入源码 -- 分析Sql执行原理

上篇说到了getwapper方法获取到了Mapper接口的代理对象,现在分析下Mybatis执行Sql的相关内容原理。

伴随着问题深入分析:

  1. Mybatis是如何通过Mapper接口找到对应的mapper.xml文件的sql执行语句?原理是什么?
  2. Mybatis怎么创建的数据库连接?什么方式创建的?
  3. Mybatis怎么进行参数处理的,原理是什么?
  4. 怎么执行sql语句的?
  5. 执行sql后结果集是怎么映射到实体类的?

 伴随着以上问题,我们渐渐深入源码探究:

 通过之两篇文章的介绍,我们知道了getMapper会返回一个MapperProxy#JDK代理对象。既然是代理对象,执行时就会执行代理对象的invoke方法 MapperProxy#invoke

进入源码:

执行Sql获取

  @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 if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // 获取Mapper方法,这里就是主要处理获取Mapper接口所对应的含有sql的mapperMethod 
    final MapperMethod mapperMethod = cachedMapperMethod(method);

    // 拿到mapperMethod后执行sql
    return mapperMethod.execute(sqlSession, args);
  }

进入cachedMapperMethod(method),参数method是接口中方法的全名。

 com.cmm.mybatissource.project.mapper.SysUserMapper.queryList(java.lang.String)

  private MapperMethod cachedMapperMethod(Method method) {
    // 方法缓存中获取mapper方法
    MapperMethod mapperMethod = methodCache.get(method);

    // 没有获取到,则取配置configuration中获取mapperMethod 
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());

     // 将获取到的mapperMethod 加入方法缓存
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

那我们就看看是如果获取到mapperMethod的。进入 new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
   // sql命令初始化,也就是找到sql内容的步骤
    this.command = new SqlCommand(config, mapperInterface, method);
   // 方法签名
    this.method = new MethodSignature(config, mapperInterface, method);
  }

这里我们主要看下new SqlCommand(配置信息Configuration,接口全限定名,方法全限定名) 

public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      // 得到方法名称 
      final String methodName = method.getName();
      // 获取接口的class类对象
      final Class<?> declaringClass = method.getDeclaringClass();
      // 决定MapperStatment
      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 {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

进入resolveMappedStatement(接口全限定名,方面名称,接口class对象,配置configuration)

private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {

      // 生成statemengID = 接口全名 +  方法名
      String statementId = mapperInterface.getName() + "." + methodName;
      // 判断configuration中是否含有改statementId 内容的MappedStatement,之前章节已经说过了
      // mapper.xml解析的时候,会将解析的配置信息全部放入 configuration,其中就包括namespace+ID
      if (configuration.hasStatement(statementId)) {
        // 获取改接口方法所定义的MappedStatement
        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;
    }
  }

这样就很清楚了,mybatis底层是通过 statemengID = 接口 + 方法名,与configuration中配置信息xml中namespace + id 进行匹配,如果有就去除含有sql信息的mappedstatement。

 

数据库链接

等于说现在Mybatis已经知道了要执行哪个sql语句了接下来就是执行处理了。

 那回过头进入 mapperMethod.execute(sqlSession, args)

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 根据sql语句类型执行
    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;
      }
     
      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);
        }
        break;
      case FLUSH:

那就进入executeForMany(sqlsession 内含所有配置信息,args参数),直接进入改法中关键方法  sqlSession.<E>selectList(command.getName(), param)

 进入 executor.query ;

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 根据参数获取需要执行的具体SQL语句
    BoundSql boundSql = ms.getBoundSql(parameterObject);

    // 因为一级缓存默认开启的,所以需要创建一个惟一的缓存键
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 调用查询方法
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

 

 进入query

 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //获取MappedStatement的缓存对象
    Cache cache = ms.getCache();
    //因为是第一次查询,所以缓存里面肯定是空的,
    //如果是第二次查询的话,且当前数据未被改动,那么这个缓存对象肯定不为空,
    //就直接根据StateMent从缓存中取出结果,不需要进行数据连接、查询等一系列操作
    //极大的提高了查询的响应速率
    if (cache != null) {
      //根据StateMent对象查询是否需要刷新缓存,其实就是验证数据是否修改过,
      //如果数据已经被修改,则MyBatis会删除缓存
      flushCacheIfRequired(ms);
      //判断条件:是否需要使用缓存以及处理处理器是否为空
      if (ms.isUseCache() && resultHandler == null) { 
        //查询是否是Callable的StateMent对象
        ensureNoOutParams(ms, key, parameterObject, boundSql);
        //数据是否已经更改?没有更改则直接查询缓存,否则查询数据库
        if (!dirty) {
          //利用读锁进行加锁,因为DefaultSqlSession是线程不安全的
          cache.getReadWriteLock().readLock().lock();
          try {
            //根据前面创建的缓存key值,获取缓存值
            @SuppressWarnings("unchecked")
            List<E> cachedList = (List<E>) cache.getObject(key);
            //如果缓存不为空则返回缓存值
            if (cachedList != null) return cachedList;
          } finally {
            //释放读锁
            cache.getReadWriteLock().readLock().unlock();
          }
        }
        //这一步其实就是线程进来的时候还有缓存,但是在查询缓存的时候,由于数据变更导致缓存被删
        //所以只能悲催的去查询数据库
        List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        //然后将这个查询过程放到事务管理器中
        tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
        //返回结果
        return list;
      }
    }
public abstract class BaseExecutor implements Executor {
 /**
 * query()方法入口
 * 此方法只是做一些条件判断,并且再一次查询局部缓存容器,看是否有缓存值
 * 因为DefaultSqlSession是线程不安全的
 **/
 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());
    //判断当前会话是否已关闭,关闭则抛出异常,因为DefaultSqlSession是线程不安全的,
    //所以多线程情况下会有这种情况
    if (closed) throw new ExecutorException("Executor was closed.");
    //判断是否需要刷新缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    //初始化返回结果
    List<E> list;
    try {
      //这个变量类似于版本号,当前线程执行数据库操作前加1,结束后减1
      queryStack++;
      //如果结果处理器为空,则再一次查询缓存
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //如果缓存结果不为空,则更新此次Statement的缓存参数
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //如果为空,则直接查询数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      //将变量减1,变成0,表示操作完成
      queryStack--;
    }
    
    //....省略部分代码
    
    //返回结果
    return list;
  }
/**
*queryFromDatabase()方法查询数据 
* 这个方法调用的doQuery()就是我们本次要找寻的答案了。
**/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //返回结果
    List<E> list;
    //存储当前的缓存唯一键key和执行器持有者放入localCache对象
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //查询数据库
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      //移除前面存储的key-value对象
      localCache.removeObject(key);
    }
    //将缓存唯一键key和查询结果放入本地缓存中
    localCache.putObject(key, list);
    //判断StatementType是否是CALLABLE类型的,就是是否需要调用存储过程
    if (ms.getStatementType() == StatementType.CALLABLE) {
      //如果是则将缓存键key和参数放入localOutputParameterCache对象中
      localOutputParameterCache.putObject(key, parameter);
    }
    //返回结果
    return list;
  }
}

进入doQuery

 @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();

    // 获取Statement处理器,StateMent处理器包含了:SQL语句、执行器、结果处理器、参数处理器等等
    // 是四大对象之一
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

    // 进行sql语句预编译
    Statement stmt = prepareStatement(handler, ms.getStatementLog());

    //Statement处理器执行SQL语句进行查询
    return handler.<E>query(stmt, resultHandler);
  }

StatementHandler 是四大对象之一,用来进行sql语句的预编译和相关参数处理工作,我们看下StatementHandler的创建:

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 创建StatementHandler 
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 拦截链调用插件
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

同样的在StatementHandler创建时我们也看到了插件的调用过程,所以我们也可以自定义插件对statementHandler 进行修改。

继续 sql语句预编译 :Statement stmt = prepareStatement(handler, ms.getStatementLog());

/**
 * 进行数据库连接,同时也进行Sql语句预编译
 **/
 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    //Statement对象
    Statement stmt;
    //进行数据库连接
    Connection connection = getConnection(statementLog);
    //进行SQL预编译处理
    stmt = handler.prepare(connection);
    //设置sql参数
    handler.parameterize(stmt);
    //返回StateMent对象
    return stmt;
  }
n.net/qq_36526036/article/details/105647571

进入 getConnection;

/**
 * 下面的这四个方法就是数据库连接的整个过程,
 * 可以看到最后一个方法就是调用JDBC的连接方法,进行数据库连接的
 * 到此也证明了我们猜想的正确性
 **/
protected Connection getConnection(Log statementLog) throws SQLException {
    //在当前事务中创建一个数据库连接
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog);
    } else {
      //返回连接
      return connection;
    }
}
 //打开一个连接
 public Connection getConnection() throws SQLException {
    //如果连接为空
    if (connection == null) {
      //新建一个连接
      openConnection();
    }
    //返回这个连接对象
    return connection;
  }
 /**
 *根据数据源信息进行数据库连接 
 **/
 protected void openConnection() throws SQLException {
    //用数据源进行数据库连接
    connection = dataSource.getConnection();
    //事务级别设置
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    //设置自动提交事务,默认为false
    setDesiredAutoCommit(autoCommmit);
  }
  /**
  * 调用JDBC方法进行数据库连接
  **/
  public Connection getConnection() throws SQLException {
        //调用数据库驱动创建一个连接     
        return DriverManager.getConnection(this.jdbcUrl, this.createProps((String)null, (String)null));
    }

进入handler.parameterize(stmt);看下,底层通过parameterHandler处理器进行了参数设置

  @Override
  public void parameterize(Statement statement) throws SQLException {
    // 参数设置
    parameterHandler.setParameters((PreparedStatement) statement);
  }

SQL执行

最终的sql语句查询是在 doQuery  return handler.<E>query(stmt, resultHandler);

/**
* 执行SQL语句入口
**/
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    //将Statement对象强转成PreparedStatement对象
    PreparedStatement ps = (PreparedStatement) statement;
    //执行SQL语句,查询结果保存在PreparedStatement对象中: 
    //这个执行过程确实和JDBC执行过程一致
    //JDBC: ps.executeQuery(sql)
    ps.execute();
    //解析查询出来的结果:将JDBC类型转换成JAVA类型
    return resultSetHandler.<E> handleResultSets(ps);
  }

 最终将查询到的数据通过 resultSetHandler处理器先进映射处理:

/**
* 解析查询结果: 已省略部分代码  
**/
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    //实例化结果集
    final List<Object> multipleResults = new ArrayList<Object>();
    //获取执行SQL返回的结果类型集合, 比如测试类返回的是:BaseUserInfoDO类
    final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    //SQL执行返回的结果类型集合长度
    int resultMapCount = resultMaps.size();
    //结果集计数器,遍历结果集用
    int resultSetCount = 0;
    //获取执行SQL的结果,返回的结果集都在这个对象里面
    ResultSet rs = stmt.getResultSet();
    //遍历结果集
    while (rs != null && resultMapCount > resultSetCount) {
      //获取第一种结果类型
      final ResultMap resultMap = resultMaps.get(resultSetCount);
      //获取查询结果列的缓存,里面存放的是查询的列和列的JDBC类型及对应的JAVA类型
      ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
      //解析返回的结果
      handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
      //获取下一个结果类型,测试类只有一个结果类型:BaseUserInfoDO类
      rs = getNextResultSet(stmt);
      resultSetCount++;
    }
    //返回结果
    return collapseSingleResultList(multipleResults);
  }
 /**
 * 解析查询结果 已省略部分代码
 **/ 
 protected void handleResultSet(ResultSet rs, ResultMap resultMap, List<Object> multipleResults, ResultColumnCache resultColumnCache) throws SQLException {
        //新建结果集对象
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        //解析SQL查询出来的所有的行信息:即所有的结果集,这个时候结果集还是JDBC类型
        handleRowValues(rs, resultMap, defaultResultHandler, rowBounds, resultColumnCache);
         //将解析后的结果集添加到这个结果对象中:这个时候结果集对应的就是JAVA类型了 
        multipleResults.add(defaultResultHandler.getResultList());
  }
  /**
  * 遍历结果集合
  **/
  protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
    //实例化一个结果对象
    final DefaultResultContext resultContext = new DefaultResultContext();
    //遍历查询出来的所有结果:即数据库表的所有行数据
    while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
       //获取结果集对象:即BaseUserInfoDO类
       final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
       //解析每一行的结果(一个BaseUserInfoDO对象):
       //即把数据表中每一行的每一个JDBC字段转换成JAVA字段并赋值
       Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
       //将这个对象添加到结果集中
       callResultHandler(resultHandler, resultContext, rowValue);
    }
  }
/**
*  解析每一行的值:即把数据表中每一行的每一个JDBC字段转换成JAVA字段并赋值
**/
protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
    final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
    //获取BaseUserInfoDO类对象
    Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
    //获取BaseUserInfoDO类对象
    final MetaObject metaObject = configuration.newMetaObject(resultObject);
    //获取BaseUserInfoDO类对象的成员变量
    final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
    //解析BaseUserInfoDO对象每一个成员变量值
    applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache) 
    //返回结果
    return resultObject;
  }
 /**
 *把数据表中每一行的每一个JDBC字段转换成BaseUserInfoDO类的成员变量并且赋值
 **/
 protected boolean applyAutomaticMappings(ResultSet rs, List<String> unmappedColumnNames, MetaObject metaObject, String columnPrefix, ResultColumnCache resultColumnCache) throws SQLException {
    //核心代码:遍历BaseUserInfoDO类的每一个成员变量,然后进行赋值
    for (String columnName : unmappedColumnNames) {
          //根据成员变量类型获取相应的类型解析器
          final TypeHandler<?> typeHandler = resultColumnCache.getTypeHandler(propertyType, columnName);
          //根据相应的类型解析器,将查询出来的JDBC类型值转换成对应的JAVA类型值
          final Object value = typeHandler.getResult(rs, columnName);
          //对字段进行赋值
          metaObject.setValue(property, value);
        }
    }
  }

以上就是Mybatis整个流程,补充下我们看下最后parameterHandler(参数处理) resultSetHandler(结果集处理)的创建源码:

 可以看到这两个处理器也是支持插件处理的。

总结:

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Survivor001

你可以相信我,如果你愿意的话

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值