This article documents the myBatis framework alone, not the Spring integration.

The test method

This method is a test method. The first step is to read the configuration file as a byte stream, focusing on the build method in sqlsessionFactoryBuilder

@Test
    public void test() throws IOException {
        InputStream resources= Resources.getResourceAsStream("SqlMapperConfig.xml");
        final SqlSessionFactory build =new SqlSessionFactoryBuilder().build(resources);
        final PaymentChannelMapper mapper = build.openSession().getMapper(PaymentChannelMapper.class);
        final List<PaymentChannel> all = mapper.findAll(5,5);
        for (int i = 0; i < all.size(); i++) {
            System.out.println(all.get(i));
        }
    }
Copy the code

Start the

The build method here is an overloaded build method that generates an XMLconfigBuilder object

public SqlSessionFactory build(InputStream inputStream, String environment, // Generate an XMLconfigBuilder object XMLconfigBuilder parser = new XMLconfigBuilder (inputStream, environment, properties); 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

XMLConfigBuilder constructor

This initializes the XMLConfigBuilder parameter, focusing on the XPathParser object

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
Copy the code

XPathParser object

Document is the root node of the XML file, Validation is enabled, variables corresponds to a set of key-value pairs under the node, and xpath parses the XML object. Then proceed to the parse method of XMLConfigBuilder.

Public class XPathParser {// Document is the root node of the XML file private final Document document; Private Boolean validation; Private EntityResolver EntityResolver; private Properties variables; //xpath parses XML objects. }Copy the code

XMLConfigBuilder. Parse method

The parser(XPathParser).evalNode method actually parses the tag into a Node object by xpath and returns it wrapped as an XNode object.

public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once.");  } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }Copy the code

Enter the parseConifguration method

PluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement pluginElement

private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); // Interceptor plugins enhance parsing of pluginelements (root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // Read it after objectFactory and objectWrapperFactory Issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); //mapper. XML parses mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); }}Copy the code

pluginElement

As you can see, traverse the child nodes of the root node, find the configured interceptor information, convert it into an interceptor object, set the parameter properties, and add them to the configuration.

private void pluginElement(XNode parent) throws Exception { if (parent ! = null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); }}}Copy the code

configuration.addInterceptor

The interceptorChain finally puts the Interceptor object into the interceptor collection of the interceptorChain object. Here the plug-in is parsed

public void addInterceptor(Interceptor interceptor) { interceptorChain.addInterceptor(interceptor); } public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<Interceptor>(); . public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); }... }Copy the code

EnvitonmentsElement method

The first step is to configure the transactionManager transactionManager, the second step is to configure the datasource, and finally encapsulate it into an environment object.

private void environmentsElement(XNode context) throws Exception { if (context ! = null) { if (environment == null) { environment = context.getStringAttribute("default"); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); if (isSpecifiedEnvironment(id)) { TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); }}}}Copy the code

MapperElement method

The mapper. XML configuration file is parsed first to determine whether it is a scan type or a URL or a class path (here I’m using reource), then loaded as a byte input stream, and then an XMLMapperBuilder object is created

private void mapperElement(XNode parent) throws Exception { if (parent ! = null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource ! = null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url ! = null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } 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

The XMLMapperBuilder object has the same properties as the XMLConfigBuilder described above

public class XMLMapperBuilder extends BaseBuilder { private final XPathParser parser; Private final MapperBuilderAssistant builderAssistant; < SQL > Private final Map<String, XNode> sqlFragments; private final String resource; }Copy the code

Parse method of xmlMapperBuilder Go to the parse method of xmlMapperBuilder to parse the mapper. XML file and check whether resources are loaded

public void parse() { if (! configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }Copy the code

ConfigurationElement method

As you can see here, we get the namespace value, throw an exception if it’s not set or empty, and then we set namesapce to the builderAssistant, which is a MapperBuilderAssistant object, The mapperbuider parsing file puts the information into Assistant for parsing. The next step is to parse and configure the other namespace caches, as well as the local namespace cache, followed by the map map of the request parameters, the map map of the parameters of the result set, and the parsing of the generic SQL statement. Finally, we begin parsing the SQL in each SQL tag, focusing here.

private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); ParameterMapElement (context.evalNodes("/mapper/parameterMap")); // parameterMapElement(context.evalNodes("/mapper/parameterMap")); // resultMapElements(context.evalNodes("/mapper/resultMap")); // resultMapElements(context.evalNodes("/mapper/resultMap")); // sqlElement(context.evalNodes("/mapper/ SQL ")); / / all SQL statements buildStatementFromContext (context. EvalNodes (" select | insert | update | delete ")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); }}Copy the code

A method to perform parsing

Private void buildStatementFromContext (List < XNode > List) {/ / multiple source if (configuration. GetDatabaseId ()! = null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); Try {/ / analytical statementParser parseStatementNode (); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); }}}Copy the code

The logic is easy to understand when you start parsing the SQL configuration, I list the place where I feel hard to understand to explain XMLIncludeTransformer includeParser = new XMLIncludeTransformer (configuration, builderAssistant); And includeParser applyIncludes (context. GetNode ()); The two methods are processSelectKeyNodes(ID, parameterTypeClass, langDriver) that parse include tags; This method parses the SelectKet tag. SqlSource object creation, this is the main logic to generate dynamic SQL, enter a look.

public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (! databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<? > parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<? > resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", ! isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); 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; } 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

CreateSqlSource method

First you create an XMLScriptBuilder object, the XMLLanguageDriver class

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<? > parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); }Copy the code

XmlscriptBuilder object

Public class XMLScriptBuilder extends BaseBuilder {private final XNode context; SQL private Boolean isDynamic; Private final Class<? > parameterType; }Copy the code
The parseScripNode method of xmlscriptBuilder
public SqlSource parseScriptNode() {
    List<SqlNode> contents = parseDynamicTags(context);
    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
    SqlSource sqlSource = null;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }
Copy the code
XmlscriptBuilder parseDynamicTags method

If it is static, it will generate a static sqlNode object. If it is dynamic, it will generate different sqlNode objects according to different labels and store them in the list collection.

List<SqlNode> parseDynamicTags(XNode node) {
    List<SqlNode> contents = new ArrayList<SqlNode>();
    NodeList children = node.getNode().getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlers(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return contents;
  }
Copy the code

SQL > create RawSqlSource; SQL > create RawSqlSource; SQL > create RawSqlSource

if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
Copy the code
sqlSource.getBoundSql

SqlSource getBoundSql (); sqlSource getBoundSql ();

GetBoundSql method of DynamicSqlSource
@Override public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<? > parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; }Copy the code
The getBoundSql method of RawSqlSource
  public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
  }
Copy the code

Back to main flow

Select * from insert statement where primary key is required

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;
    }
Copy the code

When the addMappedStatement method is called, the parsed SQL is encapsulated as a MapperStatement object and saved

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

AddMappedStatement method

public MappedStatement addMappedStatement(...) {... id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)... ; ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap ! = null) { statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }Copy the code

The configuration. AddMappedStatement method Here you can see the final will be in the form of the key – value stored in the configuration of the map in the collection, The key is namespace.id value is mappedStatement, so the mapper. XML method does not support overloading.

public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }
Copy the code

XmlMapperBuilder. Parse method

So once you’ve parsed it back here, there’s an important method called bindMapperForNamespace

 public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
...
  }
Copy the code

BindMapperForNamespace method

This method calls configuration.addMapper and you can see that it stores the object proxy factory for Type into mapperRegister and into Map. Type is the name of the namespace passed in, which is the Mapper interface object.

private void bindMapperForNamespace() { String namespace = builderAssistant.getCurrentNamespace(); if (namespace ! = null) { Class<? > boundType = null; try { boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //ignore, bound type is not required } if (boundType ! = null) { if (! configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType); }}}} // Agent factory corresponding to the interface Private final Map<Class<? >, MapperProxyFactory<? >> knownMappers = new HashMap<Class<? >, MapperProxyFactory<? > > (); public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { knownMappers.put(type, new MapperProxyFactory<T>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (! loadCompleted) { knownMappers.remove(type); }}}}Copy the code

At this point, the mybatis XML configuration file startup process is finished parsing, the next step is to execute the process.

perform

First through the SqlSessionFactoryBuilder is sqlsession openSession approach, then calls the sqlsession getMapper method. The argument passed in is the class for which the proxy object needs to be generated. You can see here what the getMapper method actually calls the MapperRegister method

GetMapper method

The proxy factory object corresponding to the object is identified from the Map collection, and the newInstance method of the factory object is called

public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); }}Copy the code

NewInstance method

MapperProxy is an interface that implements the InvocationHandler class. It determines the method. See the specific execution time.

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
Copy the code

Invoke the mapperProxy.invoke method

The invokeDefaultMethod method is invoked. If the method is Object, the invokeDefaultMethod method is invoked. If not, go to the cachedMapperMethod method. This step is mainly to get the MapperMethod method from the cache. If not, generate one and put it in the cache. Finally, the execute method of the MapperMethod method is called.

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
Copy the code

MapperMethod. The execute method

I’m going to tell you what type it is, the select in this case, and then I’m going to tell you whether I need to return a value, a resultHandler, and then I’m going to go to the corresponding execution method, where I’m going to return multiple objects which is the second if judgment.

public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && ! method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }Copy the code

MapperMethod executeForMany method

I’m going to get the request parameters, and then I’m going to see if there’s paging, which I don’t have, and I’m going to go to the else logic and I’m going to do selectList

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } // issue #510 Collections & arrays support if (! method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; }Copy the code

DefaultSqlSession selectList method

Get the corresponding MappedStatement here (which is the information for each tag parsed)

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(); }}Copy the code

CachingExecutor. Query method

And then we execute the Query method, and we look at getBoundSql.

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);
  }
Copy the code

MappedStatement getBoundSql method

The BoundSql object is retrieved from the sqlSource, which is the DynamicSqlSource.

 public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

Copy the code

Boundsql object

SQL: stores SQL; ParameterMappings Records the parameter sequence. ParameterObject Specifies a request parameter. If multiple parameters are passed in, the parameter is an object. If multiple parameters are passed in, the parameter is a ParamMap. AdditionalParameters Parameter map; This metaObject makes it easy to get or set the value of an originalObject (an incoming object parameter)

Public class BoundSql {private final String SQL; // Record parameter sequence private final List<ParameterMapping> parameterMappings; ParameterObject Specifies a single parameter. If multiple parameters are passed in, the parameterObject is a ParamMap collection. // private final map <String, Object> additionalParameters; Private Final MetaObject metaParameters; private final MetaObject metaParameters;Copy the code

DynamicSqlSource getBoundSql method

First create DynamicContext object, store some parameter SQL information, and then execute SqlNode’s Apply method

public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<? > parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; }Copy the code

SqlNode.apply / rootSqlNode.apply

This iterates through the stored sqlNode objects and executes the apply method

SqlNode collection

You can see that there are three SQLNodes in this SQL

StaticTextSqlNode.apply

The first StaticTextSqlNode is a simple String concatenation, followed by IfsqlNode DynamicContext.appendSQL

public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
  }
Copy the code
IfSqlNode.apply

ExpressionEvaluator evaluateBoolean method Here is through OgnlCache. GetValue method judging conditions and is in line with the request and returns the incoming parameters, if meet the requirements will go MixedSqlNode. Apply method, If not, it will not operate. Mixedsqlnode. apply concatenates the SQL from the qualifying tag.

public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }
Copy the code
MixedSqlNode.apply
public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }
Copy the code

DynamicSqlSource getBoundSql method

Parse #{} from SQL to? And then generate the BoundSql object.

public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<? > parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; }Copy the code

Executor. Query method

The real query method is then called, which first checks to see if caching is enabled. If not, the query method is executed directly

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, parameterObject, 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); }Copy the code

BaseExecutor. Query method

It first looks from level 1 cache (which is enabled by default), and if it can’t find it, it executes the queryFromDataBase method

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; }Copy the code

BaseExecutor queryFromDatabase method

This side is mainly the level 1 cache placeholder, and then set the level 1 cache after the query results

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;
  }
Copy the code

SimpleExecutor doQuery method

The statementHandler is generated, the SQL is preprocessed, and the statementhandler.query method is executed

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); }}Copy the code

PreparedStatementHandler. Query method

After the query result is obtained, it will be processed by ResultSetHandler

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

ResultsetHandler handleResultSets method

So here’s some transformation logic that I won’t go into. So far, the execution process of Mybatis has also been parsed. This process is parsed based on the condition of dynamic SQL.

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); }Copy the code

Finally, I will introduce the core components of Mybatis

StatementHandler encapsulates the JDBC Statement operation and is responsible for operations on the JDBC Statement, such as setting parameters

Convert the Statement result set to a List.

ParameterHandler is responsible for converting user-passed parameters into those required for JDBC Statements.

ResultSetHandler is responsible for converting the ResultSet object returned by JDBC into a collection of type List.

The Executor MyBatis Executor, the core of MyBatis scheduling, is responsible for SQL statement generation and query caching

The maintenance of the