侧边栏壁纸
博主头像
敢敢雷博主等级

永言配命,自求多福

  • 累计撰写 57 篇文章
  • 累计创建 0 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Mybatis原理---SqlSession完成sql查询

敢敢雷
2020-03-02 / 0 评论 / 0 点赞 / 1,146 阅读 / 3,132 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我删除。

在使用Mybatis时,首先创建SqlSessionFactory对象,然后通过SqlSessionFactory的openSession()方法创建一个SqlSession实例
在之前已经分析过创建过SqlSessionFactory过程,现在来看下SqlSession完成sql查询的过程把.
image.png

openSession()方法

SqlSession对象是通过SqlSessionFactory的openSession方法创建的

 SqlSession sqlSession = sessionFactory.openSession();

看下openSession的源码

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}

其参数如下

  • boolean autoCommit:是否开启JDBC事务的自动提交,默认为false。
  • Connection:提供连接。
  • TransactionIsolationLevel:定义事务隔离级别。
  • ExecutorType:定义执行器类型。
    实际上,openSession方法是通过SqlSessionFactory的实现类DefaultSqlSessionFactory覆盖openSession方法创建的
    详细源码如下:
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

调用了openSessionFromDataSource方法,继续追下去
openSessionFromDataSource源码:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      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();
    }
  }

这里的作用主要是创建的东西如下,其返回对象为一个SqlSession的实现类—DefaultSqlSession

  • 用TransactionFactory 创建一个Transaction
  • 创建一个执行器Executor;(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的)
  • 创建一个默认的SqlSession: new DefaultSqlSession(configuration, executor, autoCommit);

DefaultSqlSession:

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;
  private List<Cursor<?>> cursorList;
  ...
  ...
  SqlSession方法具体实现...
}

其他对象咱们先不管,现在SqlSession已经创建完成了

SqlSession执行sql语句过程

SqlSession之selectOne

项目代码:

	String statement = "mapper.UserMapper.selectById";
        User user = sqlSession.selectOne(statement, 1);
        System.out.println("通过id查找"+user);
        System.out.println();

<select id="selectById" resultType="domain.User" parameterType="int">
        select * from user where id = #{id}
</select>

我们知道不同的XML映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复。 原因是namespace+id是作为Map<String,MapperStatement>的key使用的,如果没有namespace,就剩下id,那么,id重复就会导致数据互相覆盖,有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就不同。
所以statement即是namespace+id组合而成,目的就是让Mybatis通过statement去寻找sql语句
点进selectOne查看源码

  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    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;
    }
  }

观察源码可以很清楚的看见,其实所谓的selectOne,最后查找的还是一个list集合,在根据list的长度判断是否为1,如果长度大于1,就抛出了异常
继续进入

List<T> list = this.<T>selectList(statement, parameter);

该方法源码:

 public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }

现在好戏开始了,RowBounds.DEFAULT是用来分页,wrapCollection是判断输入类型的源码如下

private Object wrapCollection(final Object object) {
    if (object instanceof Collection) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("collection", object);
      if (object instanceof List) {
        map.put("list", object);
      }
      return map;
    } else if (object != null && object.getClass().isArray()) {
      StrictMap<Object> map = new StrictMap<Object>();
      map.put("array", object);
      return map;
    }
    return object;
  }

观察下面这行代码, statement不就是我们之前的namespace+id组合的值,
打个断点,验证下是不是查找的sql语句

 MappedStatement ms = configuration.getMappedStatement(statement);

image.png
对比之前的xml中的sql语句,果然是获得mapper.UserMapper.selectById下的内容
现在已经获得了sql语句,观察源码,下一步的执行就是executor(执行器)了,所以在Mybatis中,它完成了sql语句的执行

Executor—执行器

继续进入,观察源码

executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

image.png
欧吼,不知道是进哪个方法了,因为它们都实现Executor接口,其实再打个断点,可以发现现在调用的是CachingExecutor类中的方法
源码:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

根据上一步,ms就是我们通过namespace+id组合得到的xml,sql内容根据方法名,可以判断出boundSql是真实执行的sql语句
其中key是缓存项,关于缓存以后会写的
继续进入方法

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 {
    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);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

因为我们是第一次执行的sql,所以缓存并不存在,直接进入下一个方法

return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

点入现在调用的就是BaseExExecutor中的方法了,源码如下

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;
  }

在一开始调用时,我们使用的resultHandler为null(Executor.NO_RESULT_HANDLER)观察代码,大概理解其他都暂时不用看,直接进入下一个方法

list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

源码如下:

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;
  }

现在依然直接进入下一层方法,进入

list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

现在它调用的是SimpleExecutor的doQuery方法,继续查看源码

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

呀,这是嘛呀,这不是jdbc里面的Statement么

JDBC之Statement

Statement statement = connection.createStatement();

接着来看下Statement是怎么创建的把
根据代码stmt = prepareStatement(handler, ms.getStatementLog());
咱们点进去瞅一眼

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;
  }

发现代码stmt = handler.prepare(connection, transaction.getTimeout());
但是,我们的Connection是怎么创建的?好像没创建过这玩意呀

JDBC之Connection

点入getConnection方法

protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

transaction这个对象哪来的???
再看看openSession方法
image.png
小样原来最开始就创建出来了,可是我们还没创建过Connection对象呀,咱们继续点入方法

 @Override
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }

终于知道了,原来是在这里创建的,有点懒汉的感觉嗷
进入openConnection方法

protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    this.connection = this.dataSource.getConnection();
    if (this.level != null) {
      this.connection.setTransactionIsolation(this.level.getLevel());
    }
  }

一目了然

Statement实例创建

Connection我们知道是从哪来的了,但是还是没有看见调用connection.createStatement(),继续下面走
进入 handler.prepare(connection, transaction.getTimeout());

public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      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);
    }
  }

继续进入方法instantiateStatement(connection);
源码如下:

protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    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);
    } else {
      return connection.prepareStatement(sql);
    }
  }

一大堆判断的语句,其实最后是直接运行的最后一句connection.prepareStatement(sql)
好了Statement出来了使用的是它的子类PreparedStatement,一切明了了

正式调用,获得数据库数据

现在Statement对象的由来也清楚了,回到doQuery方法
进入下一个handler.query(stmt, resultHandler);

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

好了一切都清楚了,最后调用resultSetHandler. handleResultSets()

public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

很明显返回值存在ArrayList中,发现方法handleResultSet(rsw, resultMap, multipleResults, null);
点进去

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

果然multipleResults.add(defaultResultHandler.getResultList());遍历结果,将结果add入list中
最后,就终于获得了数据库的值
image.png
image.png

总结

  1. MyBatis是一个半ORM框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等复杂等过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高
  2. MyBatis可以使用XML或注解来配置和映射原生信息,将POJO映射成数据库中等记录,避免了几乎所有等JDBC代码和手动设置参数以及获取结果集。
  3. 通过xml文件或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。
  4. Mybatis主要开始执行的sql语句的是Executor(执行器),Executor是跟SqlSession绑定在一起的,每一个SqlSession都拥有一个新的Executor对象,由Configuration创建。
  5. Executor分成两大类:
    • BaseExecutor

      • SimpleExecutor:每执行一次update或select,就开启一个 Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)
      • ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map<String, Statement>内,供下一次使用。(可以是Statement或PrepareStatement对象)
      • BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理的;BatchExecutor相当于维护了多个桶,每个桶里都装了很多属于自己的SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最后,再统一倒进仓库。(可以是Statement或PrepareStatement对象)
    • CachingExecutor

      • 先从缓存中获取查询结果,存在就返回,不存在,再委托给Executor delegate去数据库取,delegate可以是上面任一的SimpleExecutor、ReuseExecutor、BatchExecutor。
0

评论区