MyBatis source code analysis

A, use,

MyBatis = MyBatis = MyBatis = MyBatis = MyBatis = MyBatis = MyBatis = MyBatis = MyBatis

@Test
public void test(a) throws Exception {
    // Read the configuration file
    InputStream resourceAsStream = Resources.getResourceAsStream("resources/sqlMapConfig.xml");
    // Create SqlSessionFactory with SqlSessionFactoryBuilder
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    // Obtain the SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    / / call the specified Mapper method com. Wyh. Apply Mapper. UserMapper. QueryAll statementId
    List<User> userList = sqlSession.selectList("com.wyh.mapper.UserMapper.queryAll");
    System.out.println(userList);
}
Copy the code

Read the configuration file and convert it to an InputStream

/** * Returns a resource on the classpath as a Stream object@param loader   The classloader used to fetch the resource
 * @param resource The resource to find
 * @return The resource
 * @throws java.io.IOException If the resource cannot be found or read
 */
public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
  InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
  if (in == null) {
    throw new IOException("Could not find resource " + resource);
  }
  return in;
}
Copy the code

Create SqlSessionFactory

Create SqlSessionFactory from SqlSessionFactoryBuilder

public SqlSessionFactory build(InputStream inputStream) {
    // Call overloaded methods
    return build(inputStream, null.null);
}

/** * How to parse MyBatis config file **@paramInputStream Configuration file stream *@paramEnvironment Environment name *@paramProperties Configuration properties *@return SqlSessionFactory
 */
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    // Create XMLConfigBuilder to parse sqlmapconfig.xml configuration
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    The // parser.parse() method parses the configuration in sqlmapconfig.xml
    // The build() method builds DefaultSqlSessionFactory
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      inputStream.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.}}}Copy the code

3.1 Parsing configuration Files

XMLConfigBuilder is used to parse MyBatis sqlmapconfig.xml. Similar examples include XMLMapperBuilder, XMLStatementBuilder…. Their parent classes are baseBuilders, which are used to parse various configurations

Continuing with the code, the xmlConfigBuilder.parse method is called

public Configuration parse(a) {
    // Check whether parsing is done
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    // Mark the resolved state as resolved
    parsed = true;
    // Parse the configuration under configuration
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}
Copy the code

The parse method calls parseConfiguration() to parse the tag. This method parses properties, Settings, typeAliases, mapper…. These labels

private void parseConfiguration(XNode root) {
    try {
      // Parse the properties tag
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      // Parse the Settings tag
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      // Load the custom vfsImpl
      loadCustomVfs(settings);
      // Load custom logImpl
      loadCustomLogImpl(settings);
      // Parse the typeAliases tag
      typeAliasesElement(root.evalNode("typeAliases"));
      // Parse the plugins tag
      pluginElement(root.evalNode("plugins"));
      // Parse the objectFactory tag
      objectFactoryElement(root.evalNode("objectFactory"));
      // Parse the objectWrapperFactory tag
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // Resolve the reflectorFactory tag
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // Resolve the environments tag
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      // Parse the databaseIdProvider tag
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // Parse the typeHandlers tag
      typeHandlerElement(root.evalNode("typeHandlers"));
      // Parse the mapper tag
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e); }}Copy the code

3.2 analytical properties

private void propertiesElement(XNode context) throws Exception {
  if(context ! =null) {
    Properties defaults = context.getChildrenAsProperties();
    / / get the resource
    String resource = context.getStringAttribute("resource");
    // Get the URL attribute
    String url = context.getStringAttribute("url");
    // Resource and URL cannot exist together
    if(resource ! =null&& url ! =null) {
      throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
    }
    if(resource ! =null) {
      // Put all resources in the defaults class
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if(url ! =null) {
      // Place the properties obtained from the URL into the defaults class
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
    // Get the configuration in configuration
    Properties vars = configuration.getVariables();
    if(vars ! =null) {
      defaults.putAll(vars);
    }
    parser.setVariables(defaults);
    // Put the parsed properties back into configurationconfiguration.setVariables(defaults); }}Copy the code

3.3 analytical typeAliases

TypeAliases are used to configure aliases for parameterType and resultType in mapperXml configuration without fully qualified naming

TypeAliases configuration:

<typeAliases>
        <! -- Define a path for a single alias. Type: type alias: alias -->
        <! -- <typeAlias type="cn.wyh.entity.User" alias="user"/> -->

        <! Mybatis automatically scans the Po class in the package, automatically defines the alias, the alias is the class name (the first letter can be capital or lowercase).
        <package name="com.wyh.entity"/>
</typeAliases>
Copy the code

The typeAliasesElement method is used to parse the contents of the typeAliases tag

private void typeAliasesElement(XNode parent) {
  if(parent ! =null) {
    // Loop through all child nodes
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        // Get the package name
        String typeAliasPackage = child.getStringAttribute("name");
        / / class under scanning all packages, then register to the Configuration. The typeAliasRegistry
        configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
      } else {
        // Get the alias
        String alias = child.getStringAttribute("alias");
        // Get the type
        String type = child.getStringAttribute("type");
        try{ Class<? > clazz = Resources.classForName(type);if (alias == null) {
            / / registered under the name of the class to the Configuration. The typeAliasRegistry
            typeAliasRegistry.registerAlias(clazz);
          } else {
            / / registered under configured alias alias to the Configuration. The typeAliasRegistrytypeAliasRegistry.registerAlias(alias, clazz); }}catch (ClassNotFoundException e) {
          throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
        }
      }
    }
  }
}
Copy the code

After completion of parsing the alias information is stored in the Configuration. The typeAliasRegistry, MyBatis also comes with an alias Configuration, as follows:

public TypeAliasRegistry(a) {
    registerAlias("string", String.class);
    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);
    / /... , there are many below, you can see the source code
}
Copy the code

3.4 analytical plugins

Plugins configuration:

<plugins>
    <! -- Paging plugin -->
    <plugin interceptor="com.github.pagehelper.PageHelper">
        <property name="dialect" value="mysql"/>
    </plugin>
    
    <! -- MyBatisPlus configuration -->
    <plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor">
        <! -- Specify which generic mapper interface is currently used -->
        <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
    </plugin>
</plugins>
Copy the code

PluginElement is used to resolve the plugins Configuration, this method will all plugins Configuration register to the Configuration. The interceptorChain

private void pluginElement(XNode parent) throws Exception {
  if(parent ! =null) {
    for (XNode child : parent.getChildren()) {
      // Parse the class name
      String interceptor = child.getStringAttribute("interceptor");
      // Parse the properties property
      Properties properties = child.getChildrenAsProperties();
      // Get an instance of the class
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
      interceptorInstance.setProperties(properties);
      / / Interceptor to registration to the Configuration. The interceptorChainconfiguration.addInterceptor(interceptorInstance); }}}Copy the code

3.5 analytical typeHandlers

TypeHandlers configuration information:

<typeHandlers>
  <typeHandler handler="com.wyh.util.ExampleTypeHandler" javaType="java.util.Date" jdbcType="VARCHAR"/></typeHandlers>
Copy the code

TypeHandlerElement this method according to the Configuration of the javaType and corresponding jdbcType do then register to the Configuration. The typeHandlerRegistry

/** * Parse the typeHandlers configuration *@param parent
 */
private void typeHandlerElement(XNode parent) {
    if(parent ! =null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                // Get the configured package name
                String typeHandlerPackage = child.getStringAttribute("name");
                // Look for all classes in the package, and then scan the annotations in the class for javaTypeClass and jdbcType
                typeHandlerRegistry.register(typeHandlerPackage);
            } else {
                // Get the Java type
                String javaTypeName = child.getStringAttribute("javaType");
                // Get the JDBC type corresponding to the Java type
                String jdbcTypeName = child.getStringAttribute("jdbcType");
                / / get handlerTypeName
                String handlerTypeName = child.getStringAttribute("handler"); Class<? > javaTypeClass = resolveClass(javaTypeName); JdbcType jdbcType = resolveJdbcType(jdbcTypeName); Class<? > typeHandlerClass = resolveClass(handlerTypeName);if(javaTypeClass ! =null) {
                    if (jdbcType == null) {
                        // Register with typeHandlerRegistry
                        typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
                    } else {
                        // Register with typeHandlerRegistrytypeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); }}else {
                    // Register with typeHandlerRegistry
                    typeHandlerRegistry.register(typeHandlerClass);
                }
            }
        }
    }
}
Copy the code

3.6 analytical mapper

The mapperElement method is used to parse a mapper mapping file, and the code is as follows:

private void mapperElement(XNode parent) throws Exception {
    if(parent ! =null) {
      for (XNode child : parent.getChildren()) {
        // Configuration is all classes in a package
        if ("package".equals(child.getName())) {
          // Get the package name
          String mapperPackage = child.getStringAttribute("name");
          / / will package the next all classes to register to the Configuration. The mapperRegistry
          configuration.addMappers(mapperPackage);
        }
        // A single resource file is configured
        else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // Parse the resource configuration
          if(resource ! =null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            // Get the resource file stream
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // Parse the file with XMLMapperBuilder
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,        configuration.getSqlFragments());
            mapperParser.parse();
          }
          // Parse url resources
          else if (resource == null&& url ! =null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            // Get the resource file stream
            InputStream inputStream = Resources.getUrlAsStream(url);
            // Parse the file with XMLMapperBuilder
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          }
          // Parse the mapperClass resource
          else if (resource == null && url == null&& mapperClass ! =null) { Class<? > mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); }else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
}
Copy the code

3.6.1 XMLMapperBuilder. Parse

The XMLMapperBuilder object is used to parse mapper. XML. Its parent class is BaseBuilder.

public void parse(a) {
  // Check whether the resource has been loaded
  if(! configuration.isResourceLoaded(resource)) {// Load the mapper mapping file
    configurationElement(parser.evalNode("/mapper"));
    // Add the resource to the loaded list
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

// Parse the contents under the mapper tag
private void configurationElement(XNode context) {
  try {
    // Get the namespace attribute
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    / / parsing parameterMap
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    / / resultMap
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    // Parse the SQL fragment
    sqlElement(context.evalNodes("/mapper/sql"));
    / / parse the select | insert | update | delete tags Will eventually label content is parsed into MappedStatement and add to the Configuration. The mappedStatements
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: "+ e, e); }}Copy the code
3.6.1.1 buildStatementFromContext method

BuildStatementFromContext method is used to resolve the mapper. The XML SQL configuration:

private void buildStatementFromContext(List<XNode> list) {
  if(configuration.getDatabaseId() ! =null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    / / create XMLStatementBuilder
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      statementParser.parseStatementNode();
    } catch(IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); }}}Copy the code

Call statementParser. ParseStatementNode parse () method;

public void parseStatementNode(a) {
    // The method id
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if(! databaseIdMatchesCurrent(id, databaseId,this.requiredDatabaseId)) {
      return;
    }
    / / the name of the tag < select | insert | update | delete >
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // Check whether it is a query statement
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered".false);

    // SQL fragment in XML
    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    / / get parameterType
    String parameterType = context.getStringAttribute("parameterType");
    // Look it up in the Alias configuration typeAliasRegistry based on the parameterType name. If not, use reflection to get the classClass<? > parameterTypeClass = resolveClass(parameterType); String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    // Get statementType for Sql execution
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    // Return type
    String resultType = context.getStringAttribute("resultType"); Class<? > resultTypeClass = resolveClass(resultType);// Returns the Map type
    String resultMap = context.getStringAttribute("resultMap");
    // When retrieving the resultSetType mapping SQL results are returned
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    String resultSets = context.getStringAttribute("resultSets");

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
Copy the code

StatementParser again. ParseStatementNode () method calls the builderAssistant. AddMappedStatement method, Parses the attributes are encapsulated into stamentMap then stored to the Configuration. The mappedStatements

public MappedStatement addMappedStatement( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<? > parameterType, String resultMap, Class<? > resultType, ResultSetType resultSetType,boolean flushCache,
      boolean useCache,
      boolean resultOrdered,
      KeyGenerator keyGenerator,
      String keyProperty,
      String keyColumn,
      String databaseId,
      LanguageDriver lang,
      String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = newMappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, ! isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if(statementParameterMap ! =null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
}
Copy the code

3.7 Parsing Complete

After these Configuration files are parsed, the Configuration information is stored in the Configuration object, which is the data center of myBatis.

We come back to see the SqlSessionFactoryBuilder is. The build () method

/** * How to parse MyBatis config file **@paramInputStream Configuration file stream *@paramEnvironment Environment name *@paramProperties Configuration properties *@return SqlSessionFactory
 */
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    // Create XMLConfigBuilder to parse sqlmapconfig.xml configuration
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    The // parser.parse() method parses the configuration in sqlmapconfig.xml
    // The build() method builds DefaultSqlSessionFactory
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      inputStream.close();
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}
Copy the code

Builder method according to the Configuration object to construct a DefaultSqlSessionFactory, thus building a SqlSessionFactory building is completed

4. Obtain SqlSession

Since SqlSessionFactoryBuilder returns DefaultSqlSession, the openSession method is also in DefaultSqlSession

@Override
public SqlSession openSession(a) {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null.false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    // Get the database environment configured in the configuration file
    final Environment environment = configuration.getEnvironment();
    // Build TransactionFactory based on the database environment
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    / / build the Transaction
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    ExecType =null; SimpleExecutor is built by default
    final Executor executor = configuration.newExecutor(tx, execType);
    / / return DefaultSqlSession
    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(); }}Copy the code

Execute the specified Sql

To call the SQL specified in mapper. XML, we call sqlSession.selectOne or selectList, passing in mapper.xml.namespace + Tag ID, because it is stored in this format when parsing tags above, example code:

List<User> userList = sqlSession.selectList("com.wyh.mapper.UserMapper.queryAll");
Copy the code

DefaultSqlSession.selectList

@Override
public <E> List<E> selectList(String statement) {
  return this.selectList(statement, null);
}

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

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    // Obtain the MappedStatement according to statementId
    MappedStatement ms = configuration.getMappedStatement(statement);
    // Call the query method of BaseExecutor, the parent of SimpleExecutor
    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(); }}Copy the code

BaseExecutor.query

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

@SuppressWarnings("unchecked")
@Override
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;
    // Check if there is any in the cache
    if(list ! =null) {
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    }
    // Do not exist in cache, query database
    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 #482clearLocalCache(); }}return list;
}
Copy the code

BaseExecutor.queryFromDatabase

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 {
    // Execute the simpleExecutor.doQuery method
    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;
}
Copy the code

SimpleExecutor.doQuery

@Override
  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();
      PreparedStatementHandler is returned
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // Assign values to parameters in SQL
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally{ closeStatement(stmt); }}Copy the code

preparedStatementHandler.query

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  // Execute the SQL statement
  ps.execute();
  // Encapsulate the return result
  return resultSetHandler.handleResultSets(ps);
}
Copy the code

So far a set of MyBatis execution process is resolved.

conclusion

The execution process of MyBatis

SqlSessionFactoryBuilder -> DefaultSqlSessionFactory -> DefaultSqlSession -> SimpleExecutor -> PreparedStatementHandler -> ResultSetHandler