Mybatis源码解析之数据库连接与SQL执行

这一篇文章主要MyBatis拿到配置文件之后,即获取到数据源配置信息和SQL语句之后,然后再从源码解读分析MyBatis是如何进行数据库连接和SQL语句执行。关于MyBatis是如何解析mybatis-config.xml文件,获取到数据源的请看这篇文章:

Mybatis源码解析之数据源和SQL构建

1、背景介绍

根据官网介绍结合自己的理解,MyBatis的流程图如下所示:
在这里插入图片描述
根据流程图,在上篇文章中创建了一个测试类,并且详细介绍了MyBatis是如何解析配置文件,获取到数据源信息及SQL语句的。下面来看一下上一篇文章中的测试类:

public class TestMyBatis {

    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        //获取MyBatis配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //通过SqlSessionFactoryBuilder的build()方法获取SqlSessionFactory 对象
        //此时已将MyBatis配置文件的相关配置已获取到,即Configuration类已获取到值
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //通过SqlSessionFactory的openSession()方法获取SqlSession 对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            //通过SqlSession对象执行SQL语句
            List<BaseUserInfoDO> result = sqlSession.selectList("com.mybatis.DemoMapper.queryBaseUserInfoDO");
            System.out.println(result.toString());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            sqlSession.close();
        }
    }
}

其中下面的语句的已在上篇文章中详细解析过了。

//已解析
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

因此本篇文章主要解析以下语句,来探查MyBatis底层是如下进行数据库连接和执行SQL语句的。

//创建SQL会话
SqlSession sqlSession = sqlSessionFactory.openSession();
//执行SQL查询语句
List<BaseUserInfoDO> result = sqlSession.selectList("com.mybatis.DemoMapper.queryBaseUserInfoDO");

2、创建SqlSession

创建SqlSession其实就是根据Confiuration对象实例化一个SQL执行器,一个执行器里面需要包含数据源信息和事务管理器,然后根据Confiuration对象和实例化的SQL执行器创建一个SqlSession对象,数据库连接和SQL语句的执行都在这个SqlSession里面进行操作。
创建SqlSession对象的执行语句如下所示:

  //已解析结束
  SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  //根据sqlSessionFactory对象创建一个SqlSession对象
  SqlSession sqlSession = sqlSessionFactory.openSession();

其中openSession() 方法的源码如下图所示,已省略无关代码:

public class DefaultSqlSessionFactory implements SqlSessionFactory {
  //openSession()方法的入口
  public SqlSession openSession() {
    //调用openSessionFromDataSource()方法创建一个SqlSession对象
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
 /**
 * 此方法就是根据之前的configuration对象获取数据源信息,
 * 然后根据数据源信息和配置的事务管理器类型新建一个JDBC事务管理器,
 * 然后根据新建的事务管理器和配置的执行器类型新建一个SQL执行器,
 * 随后根据configuration和执行器实例化一个SqlSession 对象
 **/
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    //新建一个事务管理器
    Transaction tx = null;
    try {
      //获取Environment对象,里面包含数据源信息和事务管理器类型
      final Environment environment = configuration.getEnvironment();
      //获取事务管理器工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //根据数据源信息和事务管理器类型实例化一个JDBC事务管理器对象
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //根据事务管理器和执行器类型,实例化一个SQL执行器。执行器类型为Simple类型
      final Executor executor = configuration.newExecutor(tx, execType, autoCommit);
      //创建一个SqlSession对象返回
      return new DefaultSqlSession(configuration, executor);
    } 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();
    }
  }
}

这里看一下 configuration.newExecutor()方法的源码,可以从源码上了解SQL执行器的初始化过程,无关代码已省略。

public class Configuration {
 //newExecutor()方法的入口
 public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
    //判断执行类型是否为空,如果为空,则赋值为默认的执行器类型,否则执行器类型不做改变
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    //初始化一个执行器
    Executor executor;
    //根据配置的执行器类型,实例化一个执行器
    //这里可以看到共有三种执行器类型,分别为BATCH、REUSE、SIMPLE
    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) {
      //如果开启,则缓存中的SQL执行器也被设置为当前执行器类型
      executor = new CachingExecutor(executor, autoCommit);
    }
    //设置拦截器,就是在进行SQL语句执行之前可以在拦截器中做一些SQL语句方面的修改
    executor = (Executor) interceptorChain.pluginAll(executor);
    //返回执行器
    return executor;
  }
}

此时SqlSession对象已创建,那么就到了最激动人心的时刻了,看看MyBatis是如何进行根据SqlSession进行数据库连接以及SQL语句的执行的吧。

3、数据库连接

根据官网介绍,MyBatis关于数据库连接以及Sql语句的执行都是根据JDBC规范来的,如果你对JDBC很熟悉的话,那么你对以下代码应该会感到很熟悉。JDBC操作数据库的步骤一般如下:

    //使用JDBC驱动器,根据数据源进行数据库连接
    Connection conn = DriverManager.getConnection(url, user, password);
    //根据SQL语句创建一个Statement对象,常用的一般都是预编译的PreparedStatement
    PreparedStatement ps = conn.prepareStatement(sql);
    //用StateMent执行SQL语句
    ResultSet rs= ps.executeQuery("select * from user");

接下来就让我们一起来看一下,MyBatis是如何对JDBC进行封装,同时也验证一下MyBatis内部是否真的就是JDBC的执行过程。那么现在就Ddbug下面这个语句的执行过程,深入源码进行验证:

 List<BaseUserInfoDO> result = sqlSession.selectList("com.mybatis.DemoMapper.queryBaseUserInfoDO");

上面这个语句就是告诉MyBatis需要执行DemoMapper类的queryBaseUserInfoDO()方法。DefaultSqlSession这个类是SqlSession类的子类,这个类里面包含了JDBC四种StateMent
(select|insert|update|delete) 以及其它的一些方法,比如获取**Mapper.java接口、获取Configuration类等一些方法,具体方法内容,有兴趣的同学可以自行去查阅一下,这里只看测试类用到的方法,省略其它无关代码。下面来看一下sqlSession.selectList()内部源码内容:

public class DefaultSqlSession implements SqlSession {
/**
*selectList()方法的入口
**/
public <E> List<E> selectList(String statement) {
    //调用下面一个方法
    return this.<E>selectList(statement, null);
  }
  /**
  * 这个方法增加了一个默认分页的类:RowBounds
  * 起始位置: offset = 0; 
  * 查询多少条记录?  limit = 最大的整数值。 
  **/
 public <E> List<E> selectList(String statement, Object parameter) {
    //增加了一个分页的类:RowBounds
    return this.<E>selectList(statement, parameter, RowBounds.DEFAULT);
  }
  /**
  * 这个方法就是SQL语句执行的入口
  **/
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //这个方法里面就是根据传进来的参数:"com.mybatis.DemoMapper.queryBaseUserInfoDO"
      //从Configuration类来获取MappedStatement对象
      //MappedStatement对象包含了StateMentType(Prepared)和SqlType(Select)以及具体的SQL语句
      MappedStatement ms = configuration.getMappedStatement(statement);
      //然后执行器执行查询语句
      //参数分别为:(MappedStatement对象,queryBaseUserInfoDO方法所需参数,分页对象
      //,查询结果解析器(就是将JDBC对象转换为JAVA对象))
      List<E> result = executor.<E>query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

那么执行器是怎么连接数据库然后执行SQL语句的呢?Debug进去看一下executor.query()方法的源码实现:

/**
* 这个执行器是采用的缓存执行器类,具体的执行器类型其实还是SIMPLE类型
* 因为MyBait默认一级缓存是开启的,所以在创建会话的时候,执行器具体类变更为CachingExecutor
**/
public class CachingExecutor implements Executor {
  /**
  * executor.query()方法的入口
  * 这个方法主要是获取SQL语句以及创建一个缓存键
  * 这个缓存建对应的值就是此次的查询结果
  **/
  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);
  }
  /**
  * 查询方法
  * 如果存在缓存,则直接取缓存中的值,否则需要连接数据库进行查询
  **/
  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;
      }
    }
    //缓存一开始就为空,则直接查询数据库返回结果
    return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
}

delegate.query()方法源码内容如下:

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()方法查询数据库最终用到的还是SimpleExcutor执行器去操作数据库的,Simple执行器也是最初在配置文件中进行配置的执行器,到现在终于要掀开MyBatis进行数据库操作的神秘面纱,也可以验证一下它到底是不是利用JDBC操作数据库的。doQuery()源码如下:

/**
* SimpleExecutor执行器
**/
public class SimpleExecutor extends BaseExecutor {
  /**
  *doQuery()方法查询数据库
  *进行数据库连接并执行SQL语句
  **/
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    //StateMent对象
    Statement stmt = null;
    try {
      //获取配置信息
      Configuration configuration = ms.getConfiguration();
      //获取Statement处理器,StateMent处理器包含了:SQL语句、执行器、结果处理器、参数处理器等等
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
      //调用下面的prepareStatement()方法返回一个PrepareStatement对象
      //同时也进行数据库连接和SQL预编译,如果编译不通过则抛出异常
      stmt = prepareStatement(handler, ms.getStatementLog());
      //Statement处理器执行SQL语句进行查询
      return handler.<E>query(stmt, resultHandler);
    } finally {
      //关闭此次Statement的执行
      closeStatement(stmt);
    }
  }
 /**
 * 进行数据库连接,同时也进行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;
  }
}

下面通过源码来看一下数据库的具体连接,即看一下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));
    }

至此,整个数据库连接结束,也证明了MyBaits最底层其实就是调用JDBC方法进行数据库连接的。数据库连接结束后,肯定就是执行SQL语句了。那么接下来就看一下MyBatis是否也是采用JDBC方式进行SQL语句执行的呢?

4、执行SQL语句

第3节已经完成了数据库的连接了,在数据库连接结束之后,因为配置文件配置的是SIMPLE执行器,它默认的Statement是PrepareStatement,所以在执行SQL语句之前会进行预编译处理,预编译处理的语句如下所示:

    //Statement对象
    Statement stmt;
    //进行数据库连接 第3节已解析
    Connection connection = getConnection(statementLog);
    //进行SQL预编译处理
    stmt = handler.prepare(connection);

prepare()方法源码如下所示:

/**
* 方法入口 省略部分次要代码
**/
 public Statement prepare(Connection connection) throws SQLException {
      Statement statement = null;
      //预编译SQL,编译通过则返回PreparedStatement对象,否则抛出异常 
      statement = instantiateStatement(connection);
      //设置超时时间
      setStatementTimeout(statement);
      //设置查询多少行
      setFetchSize(statement);
      //返回PreparedStatement对象
      return statement;
}
/**
* instantiateStatement():预编译SQL方法
**/
protected Statement instantiateStatement(Connection connection) throws SQLException {
    //获取SQL语句
    String sql = boundSql.getSql();
    //是否要返回生成的主键,在Mysql新增(insert)的SQL语句中会执行下面的语句
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    }
    //结果处理器类型不为空,则执行下面的语句
    else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
    //本次测试类执行下面的预编译SQL方法 
    else {
      return connection.prepareStatement(sql);
    }
  }

通过源码可以看到上面预编译过程和JDBC的预编译过程一致,预编译SQL结束后,肯定就是执行SQL语句了,Sql语句的执行是通过Statement处理器进行的,如下所示:

      Configuration configuration = ms.getConfiguration();
      //获取Statement处理器,StateMent处理器包含了:SQL语句、执行器、结果处理器、参数处理器等等
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
      //数据库连接和SQL预编译结束
      stmt = prepareStatement(handler, ms.getStatementLog());
      //Statement处理器执行SQL语句进行查询
      return handler.<E>query(stmt, resultHandler);

现在就来看一下handler.query() 方法的源码,验证SQL语句的执行是否和JDBC的执行过程一致,源码内容如下:

/**
* 执行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);
  }

现在来看一下MyBatis是如何将查询出来的JDBC结果转换成对应JAVA类型的吧,resultSetHandler.handleResultSets() 方法源码如下:

/**
* 解析查询结果: 已省略部分代码  
**/
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);
        }
    }
  }

至此整个SQL语句执行结束,现在看一下查询出来的结果,即下列语句的查询结果:

List<BaseUserInfoDO> result = sqlSession.selectList("com.mybatis.DemoMapper.queryBaseUserInfoDO");

在这里插入图片描述
此时的结果和数据库中的结果一致。
有兴趣的同学可以自己跟着流程自己动手操作一遍。可以理解的更深入一些。

总结: MyBatis内部其实就是对JDBC进行封装,然后操作数据库,执行SQL语句。所以使用MyBatis操作数据库,几乎可以免除所有的jdbc代码以及参数设置和结果集转换的工作。

5、常见面试题

  1. DefaultSqlSession是线程安全的吗?
    答:DefaultSqlSession不是线程安全的,SqlSessionManager和Spring的SqlSessionTemplate是线程安全。

  2. MyBatis是如何进行分页的?
    答:MyBatis有一个内置的分页对象RowBounds,默认的是使用这个对象进行分页的。当然也可以使用sql进行分页,也可以配置拦截器进行分页。

  3. 说说你理解的MyBatis的设计模式有几种?
    答:工厂模式:SqlSessionFactory。构建模式:build;代理模式:Proxy;装饰模式:Executor;等等

发布了17 篇原创文章 · 获赞 280 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_36526036/article/details/105647571