mybatis源码分析4 - sqlSession读写数据库完全解析

1 引言和主要类

创建完sqlSession实例后,我们就可以进行数据库操作了。比如通过selectOne()方法查询数据库,如代码

// 读取XML配置文件
String resource = "main/resources/SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建sqlSessionFactory单例,初始化mybatis容器
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建sqlSession实例,用它来进行数据库操作,mybatis运行时的门面
SqlSession session = sessionFactory.openSession();
// 进行数据库查询操作
User user = session.selectOne("test.findUserById", 1);  // 访问数据库,statement为mapper.xml中的id
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

创建sqlSessionFactory单例和sqlSession实例在前两节中分析过了,下面我们着重来分析sqlSession操作数据库的过程。

sqlSession操作数据库有两种方法,可以直接使用select update insert delete等方法;也可以通过getMapper()先获取mapper动态代理实例,然后再进行数据库操作。相比而言mapper方式更灵活且不易出错,是mybatis推荐的方式。本节我们分析直接使用select等方法的流程,下一节再分析mapper方式。

本节以selectOne()方法的实现过程为例来分析sqlSession操作数据库的流程,涉及的主要类如下。

  1. DefaultSqlSession:SqlSession的默认实现,其方法基本都是利用Executor代理实现。
  2. Executor:mybatis运行的核心,调度器。调度mybatis其他三大组件的执行。
  3. StatementHandler:SQL语句执行器,cache的管理等
  4. ParameterHandler:入参处理器,statementType为PREPARE是需要使用到它,来解析入参到preparedStatement中
  5. ResultSetHandler:结果集映射处理器,将数据库操作原始结果(主要是查询操作),映射为Java POJO。这正是ORM要解决的关键问题。

2 流程

2.1 DefaultSqlSession的selectOne()

先从DefaultSqlSession的selectOne()方法看起。

public <T> T selectOne(String statement, Object parameter) {
  // selectOne本质上是调用selectList实现,如果结果集大于一个,则报TooManyResultsException。
  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;
  }
}

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

  // 由sql语句的标示statement和入参parameter,查询满足条件的数据列表
  // @Param statement: mapper.xml中mapper节点下的select delete update insert等子节点的id属性
  // @Param parameter: 传入sql语句的入参
  // @Param rowBounds: 逻辑分页,包含offset和limit两个主要成员变量。mybatis分页逻辑为舍弃offset之前条目,取剩下的limit条。默认DEFAULT不分页
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // 从mappers节点初始化阶段创建好的mappedStatements这个Map中,找到key为当前要找到的sql的id的那条
      MappedStatement ms = configuration.getMappedStatement(statement);

      // 通过执行器Executor作为总调度来执行查询语句,后面以BaseExecutor来分析。
      // BatchExecutor ReuseExecutor SimpleExecutor均继承了BaseExecutor
      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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

selectOne()方法其实是通过调用selectList()实现的,因为二者本质是完全相同的,只是前者返回一个对象,而后者为列表List而已。selectList()采用代理模式,使用调度器Executor的query()方法实现。上一节着重讲过Executor是SqlSession各个方法的具体实现,是mybatis运行的核心,通过调度StatementHandler ParameterHandler ResultSetHandler三个组件来完成sqlSession操作数据库的整个过程。Executor的实现类有SimpleExecutor ReuseExecutor BatchExecutor等,它们的基类都是BaseExecutor。下面来分析BaseExecutor的query

2.2 BaseExecutor的query() 调度器开始数据库query

// BaseExecutor的查找方法
 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   // 从MappedStatement中找到boundSql成员变量,前面SqlSessionFactory创建部分讲到过Mapper解析时的三大组件:MappedStatement SqlSource BoundSql
   // 其中BoundSql通过sql执行语句和入参,来组装最终查询数据库用到的sql。
   BoundSql boundSql = ms.getBoundSql(parameter);

   // 创建CacheKey,用作缓存的key,不用深入理解。
   // sql的id,逻辑分页rowBounds的offset和limit,boundSql的sql语句均相同时(主要是动态sql的存在),也就是组装后的SQL语句完全相同时,才认为是同一个cacheKey
   CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);

   // 真正的查询语句执行处,关键代码
   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

// 查找方法
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());
    // 调度器已经close了则报错
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }

    // flush cache, 即写入并清空cache。之后就只能从数据库中读取了,这样可以防止脏cache
    // localCache和localOutputParameterCache为BaseExecutor的成员变量,它们构成了mybatis的一级缓存,也就是sqlSession级别的缓存,默认是开启的。
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }

    // 从缓存或数据库中查询结果list
    List<E> list;
    try {
      // queryStack用来记录当前有几条同样的查询语句在同时执行,也就是并发
      queryStack++;
      // 未定义resultHandler时,先尝试从缓存中取。
      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) {
        // statement级别的缓存,只缓存id相同的sql。当所有查询语句和延迟加载的查询语句均执行完毕后,可清空cache。这样可节约内存
        clearLocalCache();
      }
    }
    return list;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

调度器的query方法先从MappedStatement中获取BoundSql,它包含了sql语句和入参对象等变量,再构造缓存的key,即cacheKey。然后先尝试从缓存中取,缓存未命中则直接从数据库中查询。最后处理延迟加载,直接从缓存中取出查询数据即可。下面我们着重分析直接从数据库中查询的过程,也即queryFromDatabase()方法。

// 直接从数据库中查询
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  // 先利用占位符将本次查询设置到本地cache中,个人理解是防止后面延迟加载时cache为空
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    // 真正的数据库查询,后面详细分析
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    // 查到了结果后,将前面的占位符的cache删掉
    localCache.removeObject(key);
  }

  // 将查询结果放到本地cache中缓存起来
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

doQuery()进行真正的数据库查询,它由SimpleExecutor等具体类来实现。我们以SimpleExecutor为例分析。

// 数据库查询
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,用来执行sql语句。SimpleExecutor创建的是RoutingStatementHandler。
    // 它的是一个门面类,几乎所有方法都是通过代理来实现。代理则由配置XML settings节点的statementType区分。故仅仅是一个分发和路由。后面详细分析
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

    // 构造Statement,后面详细分析
    stmt = prepareStatement(handler, ms.getStatementLog());

    // 通过语句执行器的query方法进行查询, 查询结果通过resultHandler处理后返回。后面详细分析
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

doQuery流程为

  1. 先创建StatementHandler语句处理器。前面讲过StatementHandler是mybatis四大组件之一,负责sql语句的执行。根据XML配置文件的settings节点的statementType子元素,来创建不同的实现类,如SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler。他们的基类统一为BaseStatementHandler,外观类为RoutingStatementHandler(后面详细分析)。
  2. 创建完StatementHandler后,调用prepareStatement进行初始化,
  3. 然后调用实现类的query方法进行查询。

2.3 StatementHandler的query(), 语句处理器进行查询

下面我们来看StatementHandler是如何执行的,先看StatementHandler的创建过程。

2.3.1 StatementHandler的创建过程

// 创建RoutingStatementHandler,它是StatementHandler的外观类,也是StatementHandler的一个实现类
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  // 直接构造一个RoutingStatementHandler
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  // 将statementHandler,添加为插件的目标执行器。插件通过配置XML文件的plugins节点设置。
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

// RoutingStatementHandler的构造器,根据statementType变量来创建不同的StatementHandler实现,作为它的代理
// RoutingStatementHandler的几乎所有方法都是通过这些代理实现的,典型的代理模式。
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 根据statementType创建不同的StatementHandler实现类。statementType是在xml配置文件的settngs节点的statementType子元素中设置的。
    switch (ms.getStatementType()) {
      case STATEMENT:
        // 直接操作sql,不进行预编译。此时直接进行字符串拼接构造sql String
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        // 默认的类型。预处理,需要进行预编译。可以使用参数替换,会将#转换为?,再设置对应的参数的值。
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        // 执行存储过程
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

2.3.2 StatementHandler的初始化

StatementHandler的初始化如下

// 通过事务构造sql执行语句statement,如JdbcTransaction
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;

  // 开启数据库连接,创建Connection对象。JdbcTransaction事务直接通过JDBC创建connection
  Connection connection = getConnection(statementLog);

  // 初始化statement并设置期相关变量,不同的StatementHandler实现不同。后面以RoutingStatementHandler为例分析
  stmt = handler.prepare(connection, transaction.getTimeout());

  // 设置parameterHandler,对于SimpleStatementHandler来说不用处理
  handler.parameterize(stmt);
  return stmt;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

StatementHandler初始化步骤如下:

  1. 先开启一个数据库连接connection,
  2. 然后初始化statementHandler,
  3. 最后进行参数预处理。

先开启数据库连接connection,直接获取数据源dataSource的connection,即通过数据库本身来开启连接。

// JdbcTransaction和ManagedTransaction都是直接调用dataSource的getConnection
protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

再进行初始化statementHandler,调用基类BaseStatementHandler的prepare方法完成

// 初始化statementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  // 典型的代理模式,不同的statementType创建不同的Statement,但这儿他们都调用到他们的基类BaseStatementHandler中的prepare方法
  return delegate.prepare(connection, transactionTimeout);
}

// BaseStatementHandler初始化statement并设置期相关变量,不同的StatementHandler实现不同。
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // 初始化statement,由具体的StatementHandler来实现。比如SimpleStatementHandler通过JDBC connection的createStatement来创建
      statement = instantiateStatement(connection);

      // 设置timeout(超时时间)和fetchSize(获取数据库的行数)
      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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

最后进行参数预处理。不同的statementHandler实现类有不同的参数预处理方式。

  1. SimpleStatementHandler不进行任何参数预处理,它的sql直接通过字符串拼接而成。
  2. PreparedStatementHandler进行预处理,会将#转换为?,然后设置对应的变量到sql String中。
// RoutingStatementHandler的parameterize方法,通过代理模式实现
public void parameterize(Statement statement) throws SQLException {
  // 又是代理模式,由具体的statementHandler实现类来实现
  delegate.parameterize(statement);
}

// SimpleStatementHandler不做参数预处理
public void parameterize(Statement statement) throws SQLException {
    // N/A
}

// PreparedStatementHandler进行参数预处理,通过parameterHandler实现
public void parameterize(Statement statement) throws SQLException {
   // parameterHandler可以由用户通过插件方式实现,mybatis默认为DefaultParameterHandler。这个方法我们不进行详细分析了。
    parameterHandler.setParameters((PreparedStatement) statement);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2.3.3 statementHandler的query()进行数据库查询

创建和初始化statementHandler后,就可以调用它的query()方法来执行语句查询了。先看SimpleStatementHandler的query过程。

// SimpleStatementHandler的query操作过程
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  // 获取存放在boundSql中的sql执行语句
  String sql = boundSql.getSql();

  // 通过JDBC sql的statement,直接执行sql语句。入参在statement预编译时进行了转换并设置到statement中了。
  statement.execute(sql);

  // resultSetHandler处理查询结果,并返回。这一部分十分复杂,但也体现了mybatis的设计精巧之处,可以兼容很多复杂场景下的数据库结果转换。如数据库列名和Java POJO属性名不同时的映射,关联数据库的映射等。
  return resultSetHandler.<E>handleResultSets(statement);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

PrepareStatementHandler的query操作过程如下

// PrepareStatementHandler的query操作过程
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  // PREPARE方式下,sql statement进行了预编译,并注入了入参。它是一个PreparedStatement类型
  PreparedStatement ps = (PreparedStatement) statement;

  // 直接调用JDBC PreparedStatement的execute方法操作数据库。大家应该对这儿很熟悉了,JDBC的操作
  ps.execute();

  // 结果集处理,后面详细分析
  return resultSetHandler.<E> handleResultSets(ps);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

query先从boundSql中获取具体执行语句,然后通过JDBC的statement直接执行SQL语句。这两步完成后,就从数据库中查找到了结果集了。从这儿可见,mybatis最底层还是通过JDBC来操作数据库的。

mybatis对结果集的处理十分复杂,我们下面详细分析。

2.4 ResultSetHandler处理数据库结果集

通过JDBC完成数据库的操作后,我们就拿到了原始的数据库结果了。此时要将数据库结果集ResultSet转换为Java POJO。这一步通过mybatis四大组件之一的ResultSetHandler来实现。ResultSetHandler默认实现类为DefaultResultSetHandler,用户也可以通过插件的方式覆盖它。插件在xml配置文件的plugins子节点下添加。下面详细分析DefaultResultSetHandler的handleResultSets()方法。

// DefaultResultSetHandler通过handleResultSets处理数据库结果集,处理后作为真正的结果返回。此处的关键是处理resultMap映射
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;

    // 1 从JDBC操作数据库后的statement中取出结果集ResultSet
   ResultSetWrapper rsw = getFirstResultSet(stmt);

    // 2 获取resultMaps, mapper.xml中设置,并在mybatis初始化阶段存入mappedStatement中。
    // resultMap定义了jdbc列到Java属性的映射关系,可以解决列名和Java属性名不一致,关联数据库映射等诸多问题。
    // 它是mybatis中比较复杂的地方,同时也大大扩展了mybatis的功能
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);

    // 3 一条条处理resultSet
    while (rsw != null && resultMapCount > resultSetCount) {
      // 取出一条resultMap,即结果映射
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 进行数据库列到Java属性的映射,后面详细分析
      handleResultSet(rsw, resultMap, multipleResults, null);
      // 取出下一条resultSet
      rsw = getNextResultSet(stmt);
      // 清空nestedResultObjects,即嵌套的Result结果集
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    // 4 处理嵌套的resultMap,即映射结果中的某些子属性也需要resultMap映射时
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        // 取出父ResultMapping,用于嵌套情况
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          // 通过嵌套ResultMap的id,取出ResultMap
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);

          // 处理ResultSet,后面详细分析
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    // 5 构造成List,将处理后的结果集返回
    return collapseSingleResultList(multipleResults);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

handleResultSets流程如下

  1. 从JDBC操作数据库后的statement中取出结果集ResultSet
  2. 获取resultMaps, 它们定义了数据库结果集到Java POJO的映射关系
  3. 一条条处理resultSet,调用handleResultSet做数据库列到Java属性的映射
  4. 处理嵌套的resultMap,即映射结果中的某些子属性也需要resultMap映射时
  5. 构造成List,将处理后的结果集返回

这其中的关键是handleResultSet()方法进行数据库列到Java属性的映射,也是ORM关键所在。我们接着分析。

// 通过resultMap映射,处理数据库结果集resultSet
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
  try {
    if (parentMapping != null) {
      // parentMapping不为空,表示处理的是嵌套resultMap中的子resultMap。handleRowValues后面详细分析
      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
    } else {
      // 非嵌套resultMap
      if (resultHandler == null) {
        // 用户没有自定义resultHandler时,采用DefaultResultHandler。并将最终的处理结果添加到multipleResults中
        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
        multipleResults.add(defaultResultHandler.getResultList());
      } else {
        // 用户定义了resultHandler时,采用用户自定义的resultHandler
        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
      }
    }
  } finally {
    // issue #228 (close resultsets)
    closeResultSet(rsw.getResultSet());
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

handleResultSet针对嵌套resultMap和非嵌套resultMap做了分别处理,但都是调用的handleRowValues()方法,接着看。

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
  if (resultMap.hasNestedResultMaps()) {
    // 有嵌套resultMap时
    ensureNoRowBounds();
    checkResultHandler();
    handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  } else {
    // 无嵌套resultMap时
    handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

嵌套resultMap的处理比较麻烦,这儿不分析了,我们看非嵌套的,即handleRowValuesForSimpleResultMap。

// 非嵌套ResultMap的处理方法。根据resultMap一行行处理数据库结果集到Java属性的映射
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
    throws SQLException {
  DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();

  // mybatis的逻辑分页规则为跳过rowBounds中offset之前的部分,取limit行数的数据
  // skipRows方法会跳过rowBounds中offset之前的部分。
  skipRows(rsw.getResultSet(), rowBounds);

  // 一行行处理数据库结果集,直到取出的行数等于rowBounds的limit变量(逻辑分页),或者所有行都取完了。
  while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
    // 处理resultMap中的discriminator,使用结果值来决定使用哪个结果映射。可以将不同的数据库结果映射成不同的Java类型。此处不详细分析了
    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
    // 处理这一行数据, 得到映射后的Java结果
    Object rowValue = getRowValue(rsw, discriminatedResultMap);
    // 使用resultHandler处理得到的Java结果,这才是最终返回的Java属性值。
    // 用户可自定义resultHandler,否则使用DefaultResultHandler。
    // 用户可使用ResultSetHandler插件来自定义结果处理方式,此处体现了mybatis设计精巧之处
    storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
  }
}

// 逻辑分页rowBounds。skipRows方法会跳过rowBounds中offset之前的部分
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
    if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
      if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
        rs.absolute(rowBounds.getOffset());
      }
    } else {
      for (int i = 0; i < rowBounds.getOffset(); i++) {
        rs.next();
      }
    }
 }

// 逻辑分页rowBounds。shouldProcessMoreRows取limit条数据库查询结果
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) throws SQLException {
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

mybatis的逻辑分页规则为跳过rowBounds中offset之前的部分,取limit行数的数据。通过skipRows()和shouldProcessMoreRows()两个方法共同完成这个功能。遍历resultSet,通过getRowValue()方法处理一行行数据。最后调用resultHandler来处理转换后的结果。

storeObject结果处理的代码如下

// 利用resultHandler处理经过resultMap映射的Java结果
private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
    if (parentMapping != null) {
     // 嵌套的resultMap,也就是子resultSet结果。链接到父resultSet中,由父resultMap一起处理。不详细分析了
      linkToParents(rs, parentMapping, rowValue);
    } else {
      // 不是嵌套时,直接调用resultHandler进行最后的处理,后面详细看
      callResultHandler(resultHandler, resultContext, rowValue);
    }
}

private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
  // 构建resultContext上下文,然后利用resultHandler处理。后面以DefaultResultHandler来分析
  resultContext.nextResultObject(rowValue);
  ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}

// ResultHandler对映射后的结果做最后的处理
public void handleResult(ResultContext<? extends Object> context) {
  // DefaultResultHandler对经过resultMap映射后的Java结果不做任何处理,仅仅添加到list中,最后将list返回给selectList()等方法。
  list.add(context.getResultObject());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3 总结

mybatis操作数据库的流程,也是它的四大组件Executor StatementHandler ParameterHandler ResultSetHandler的执行过程。其中Executor是调度器,StatementHandler为SQL语句执行器,ParameterHandler为入参执行器,ResultSetHandler为结果集映射执行器。四大组件分层合理,运行流程清晰,都有默认实现,同时用户也可以利用plugin来覆盖它。这些无一不体现了mybatis的灵活和设计精巧,值得我们平时设计构架时学习。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页