Mybatis source code analysis "a"

Lead

In the current daily development, using a framework such mybatis, many programmers can not be avoided. Most people know the role mybatis is to avoid almost all of the JDBC code and manual setting parameters and get the result set. Because the use of Java came into contact with the operation of the database, we are using JDBC.

Since With persistence framework persistence framework is already using "natural", although we are already out of use JDBC stage, but after all, is the knowledge base, so this article will start from JDBC. In fact Mybatis package for JDBC is carried out. Then get on it!

First, the original use of JDBC

Nonsense is not the majority, the first to sections of the code to illustrate the problem:

public class TestMain {
    public static void main(String[] args) throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "zfy123456");
        connection.setAutoCommit(false);
        PreparedStatement ps = connection.prepareStatement("insert into dept values(?,?,?)");
        ps.setInt(1,10000);
        ps.setString(2,"test");
        ps.setString(3,"test");
        try{
            ps.executeUpdate();
        }catch (Exception e) {
            connection.rollback();
            e.printStackTrace();
        }finally {
            if(ps != null) {
                ps.close();
            }
            if (connection != null) {
                connection.close();
            }
        }


    }
}

For the above code which, generally, it is the process:

  1. Load drivers and initialization
  2. Connect to the database
  3. Execute SQL statements
  4. Response from the database and returns the processing result
  5. The last release of resources

Two, Mybatis operation database

mybatis learning everyday documents: http://www.mybatis.org/mybatis-3/zh/index.html , the following code reference mybatis official website.

Test categories:

public class MybatisTest {

    @Test
    public void test() throws Exception {

        User user = new User();
        user.setAddress("北京市海淀区");
        user.setBirthday(new Date(2000-10-01));
        user.setSex("男");
        user.setUsername("李清源");

        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        sqlSession.insert("insertUser", user);
        sqlSession.commit();
        sqlSession.close();

    }
}

Entity classes:

public class User {
    private int id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    // 省略get、set、toString方法
}

Mapper interfaces:

public interface UserMapper {
    void insertUser(User user) throws Exception;
}

Mapper configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.zfy.mybatis.mapper.UserMapper">
    <insert id="insertUser">
      insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address})
    </insert>
</mapper>

mybatis profile:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>

    <!-- 数据库连接配置文件 -->
    <properties resource="config.properties"> </properties>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <package name="com.zfy.mybatis.bean"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="com.zfy.mybatis.mapper"/>
    </mappers>
</configuration>

Here is omitted because too many codes on the first config.properties configuration file, refer to the Internet themselves. As can be seen from the above test class code, mybatis operation flow roughly as follows:

  • Read mybatis profile
  • The build method SqlSessionFactoryBuilder stream file according to the read information, to create Configuration object, and the data stored therein.
  • Then create an object that provides properties SqlSession
  • SQL execution
  • SqlSession.commit()
  • SqlSession.close()

Third, the core configuration file is loaded mybatis

For Resources.getResourceAsStream ( "mybatis-config.xml") tag, the configuration file is loaded on to the code of the input stream, will not go, build method SqlSessionFactoryBuilder view it directly. How to look at when loaded mybatis core configuration file. It would first look at the source build process:

SqlSessionFactoryBuilder.java
  // 调用读取流的方法入口,这里的读取流就是指向所创建的工程中的核心配置文件
  public SqlSessionFactory build(InputStream inputStream) {
    // 调用重载的build方法,这三个参数的含义分别是:读取的配置文件的信息、将要指定的环境、所要使用的web的属性文件,
    // 不过这里后面两个参数都是为null
    return build(inputStream, null, null);
  }
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 首先创建XML解析对象,这对象实际上是对XPathParser封装的工具对象,这个对象主要是针对核心配置文件进行相关读取的
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // parser.parse()才是对XML进行真正的解析,解析完之后然后调用重载方法把parser对象放到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.
      }
    }
  }

The above code is first passed through the inputStream, environment, properties XMLConfigBuilder these parameters to create the object, then the object whine parse () method to parse, the final parsed the object into the object DefaultSqlSessionFactory go with. code show as below:

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

Speaking in front of parser.parse () is the core configuration file parsing mybatis, then continue to look at the code of this method in the end what has been done. code show as below:

 

XMLConfigBuilder.java 
  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // parser.evalNode("/configuration")是为了定位核心配置文件中'configuration'元素的节点(根目录标签)
    // 在获得根标签之后,然后对根标签下的信息进行解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

The above code first only done to prevent a multi-threaded load operation, and then parsed set to true, then navigate to the root tag configuration mybatis core configuration files in the root tag after locating good, then all of its root word labels tag parsed one by one. Finally returns a configuration object. It continues to look at how parseConfiguration method is to resolve all the labels in the root word label. Here it is only the first word of the mappers tag is parsed, the core configuration for the start of the code in the above properties, typeAliases, environments, it would not be a repeat, otherwise this article would be too long. Ado, we continue to look at the code:

  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"));
      pluginElement(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"));
      // 解析mappers子标签
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

The above is the code word of all labels in the label configuration root parsing, here as an example to resolve mappers tag. It continues to look mapperElement (root.evalNode ( "mappers")) method:

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      // 因为mapper标签中的子标签存在两种写法,分别是:package、mapper
      for (XNode child : parent.getChildren()) {
        // 如果子标签的存在"package"的名称,则走此段代码
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          // 在和获取mapperPackage信息后,然后把它添加到configuration对象中,其实mapperPackage就是当前文件的路径
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // 在获取到resource、url、mapperClass信息之后,下面便对这些信息是否存在进行判断,然后走相应的逻辑代码
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 当resource != null时,在获取到对应的resource信息,后然后放到新建的XMLMapperBuilder对象中
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 最后通过mapperParser对象去解析,这后面所做的一切工作就是把mapper文件中的信息解析出来后,然后放到configuration对象中去,为后续最准备
            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);
            // 同"package".equals(child.getName())的情况
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
}

The code first judging parent! = Null case, only to follow up, otherwise do nothing. When the case is not null, the child began to label all under the mappers traverse and parse. Conditions latter operation generally flow is, the name "package" If there is a child tag, the implementation of the relevant codes, otherwise go else code in the else code block, first obtain esource, url, mapperClass, then each is empty of , implementation of the relevant code. Look at the specific comments in the code. Since the beginning of the sub-tab under the given configuration file mappers tag is a package, we are all here under this code only logical resolution.

When the tag is a child package, to get their mapperPackage, then put mapper years. The main work here in onfiguration.addMappers (mapperPackage), it would continue to look at this piece of code.

Configuration.java 

  public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }

MapperRegistry.java

  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
  public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    // 获取此路径下后缀为.lass 的文件
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

The above code, first obtain the suffix .lass file, and then then pass the information to these files Set, finally traversing Set, while calling addMapper (mapperClass) method.

  public <T> void addMapper(Class<T> type) {
    // 判断所获取到的类类型是否为Interface类型
    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.
        // 将type和config信息放到新建的MapperAnnotationBuilder对象中,config中主要包含environment这些信息
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        // 然后继续解析
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

The above code, the lock is first determined whether the acquired type of the interface classes, if the first loadCompleted is false, then later re-created MapperAnnotationBuilder type by type and config knownMappers to go into the key, where the object is design notes, because we did not use notes, not go into here. Then look parser.parse () code is how to do the operation.

MapperAnnotationBuilder.java
  public void parse() {
    String resource = type.toString();
    // 判断configuration是否包含resource信息
    if (!configuration.isResourceLoaded(resource)) {
      // 重点:这里才真正的加载后缀为.xml文件的信息
      loadXmlResource();
      // 把resource添加到configuration中
      configuration.addLoadedResource(resource);
      // 设置MapperBuilderAssistant当前的namespace
      assistant.setCurrentNamespace(type.getName());
      // 解析缓存
      parseCache();
      // 解析缓存引用
      parseCacheRef();
      Method[] methods = type.getMethods();
      // 对接口中的方法进行解析
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    // 解析方法
    parsePendingMethods();
  }

The above code is very clear logic first determines whether the configuration information contains the resource, if it does not, then continue with the subsequent processes. When entering follow-up process, first of all it is to load xml, here's the official start of the loading xml mapper. Then a number of settings and resolution cache and some other things. Here the main look loadXmlResource () this method:

  private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    // 判断configuration中是否包含了"namespace:" + type.getName())的mapper文件
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      // 获取到后缀信息为.xml的文件路径
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
        // 获取mapper文件流
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
        // 如果流信息不为空,把流信息、assistant.getConfiguration()、xmlResource、configuration.getSqlFragments()和type的name放到XMLMapperBuilder中
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        // 然后进行解析
        xmlParser.parse();
      }
    }
  }

Here there is no complex logic, or simply to determine if it contains, and then obtains suffix .xml file, when the lock is acquired by the acquired after xmlResource and ype.getClassLoader () to get files of these two parameters input stream, after obtaining the input stream, and then by entering some relevant information flow, to a new xml parsing objects, the new after completion of an analytical method to resolve this object, then take a look at this method:

XMLMapperBuilder.java
  public void parse() {
    // 判断resource是否在configuration中
    if (!configuration.isResourceLoaded(resource)) {
      // 1.首先定位到mapper文件中的根节点mapper,2.然后对该节点下的所有节点进行解析
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      // 绑定mapper到工作空间
      bindMapperForNamespace();
    }

    // 解析mapper文件中ResultMaps节点下的信息
    parsePendingResultMaps();
    // 解析缓存引用
    parsePendingCacheRefs();
    // 解析Statement
    parsePendingStatements();
  }

This code is very simple, nothing more than the logical methods to be invoked, but the focus here will be focusing on or configurationElement (parser.evalNode ( "/ mapper")) This method, where they start for each mapper file a formal call to look at this method specifically what to do. code show as below:

  private void configurationElement(XNode context) {
    try {
      // 获取mapper节点的namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      // 接下来就是对mapper节点下的各种节点进行解析了,不准备赘述,但或许讲下buildStatementFromContext方法
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析"select|insert|update|delete"等标签的信息
      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);
    }
  }

The above code is basically parsing of the configuration file of mapper some of the labels, since I started the configuration file provided by the involved <insert labels, then here is only concerned with the parsing code labels, see the code:

  private void buildStatementFromContext(List<XNode> list) {
    // 如果configuration.getDatabaseId() != null,走此段代码,否则跳过
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    // 上面的判断是判断是否存在其他数据源的设置,我们这里没有设置,所以就看这段代码了
    buildStatementFromContext(list, null);
  }
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      // 新建XMLStatementBuilder对象,这个对象包含信息有configuration(这个对象貌似无处不在)、builderAssistant(mapper resource这些重要信息)、所读取到的insert这些标签的信息(context)、
      // 最后就是数据库的操作SQL信息
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // statement节点的解析
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

Two pieces of code above logic is very simple, or just make a simple judgment, meet the relevant conditions on the implementation of the relevant logic code, buildStatementFromContext method traversed list of contents, in fact, "select | insert | update | delete "such as SQL templates under the label, and then create XMLStatementBuilder objects by some of the relevant parameters, and then call statementParser.parseStatementNode () after new objects, continue to look at the code of this method:

XMLStatementBuilder.java
  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();
    // 获取SQL命令的类型
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // 判断当前的SQL命令类型是否是select类型的
    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
    // 关于include标签的解析
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    // 解析 selectKey 标签
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    // 解析 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;
    }

    // 上面所有所获取到的信息,都是在这里使用的,到这里select|insert|update|delete这些标签的解析应该算是差不多了
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

This code seems to fancy really complicated, because here it comes to analyzing data acquired and labels of many parameters, but in this code now need to focus on just SqlSource sqlSource = langDriver.createSqlSource (configuration, context, parameterTypeClass) which lines of code, other code functions please see the comments now, interested students can go to take a closer look at yourself inside the code.

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

This code calls nothing to say, the main thing is builder.parseScriptNode () of. code show as below:

XMLScriptBuilder.java
  public SqlSource parseScriptNode() {
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
    // 判断是否是动态SQL
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      // 不是动态SQL
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

Here it is to determine whether the use of dynamic SQL, here is not dynamic SQL, then look at non-dynamic logic code.

RawSqlSource.java
  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    // 在这个构造函数里做getSql的操作
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }
  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    // 生成SQLSource解析器
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    // 对SQL进行具体的解析,这里的sqlSource中包含sql语句、字段属性映射信息、configuration信息
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
  }

 

The first method above code, the main concern is getSql (configuration, rootSqlNode) this method, the following is that this constructor, first to continue to explore it!

  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    // 根据configuration对象,创建一个动态对象
    DynamicContext context = new DynamicContext(configuration, null);
    // 把节点中SQL模板变成一个字符串
    rootSqlNode.apply(context);
    return context.getSql();
  }

This code or just create an object based on a number of parameters, context.getSql () and rootSqlNode.apply (context) is really just the configuration file of SQL template becomes a string of code students can look at themselves. Then we look at what has been done constructor code. You may create a configuration according SQLSource parser object constructor code, and then be analyzed by the particular sqlSourceParser.parse (sql, clazz, new HashMap <String, Object> ()) method. code show as below:

SqlSourceBuilder.java
  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    // 解析SQL模板中的#{username},#{birthday},#{sex},#{address}这些标签
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    // 开始真正的SQL解析,其实就是字符串拼接过程,不信你点进去看看
    String sql = parser.parse(originalSql);
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }

First create ParameterMappingTokenHandler this object in this code, and then by the object parsing SQL template # {username}, # {birthday}, # {sex}, # {address} These labels first, and then start the real SQL parsing , in fact, string concatenation process, do not believe you go to see the point.

GenericTokenParser.java
  public String parse(String text) {
    if (text == null || text.isEmpty()) {
      return "";
    }
    // search open token
    int start = text.indexOf(openToken, 0);
    if (start == -1) {
      return text;
    }
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    while (start > -1) {
      if (start > 0 && src[start - 1] == '\\') {
        // this open token is escaped. remove the backslash and continue.
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        // found open token. let's search close token.
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {
          if (end > offset && src[end - 1] == '\\') {
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {
            expression.append(src, offset, end - offset);
            offset = end + closeToken.length();
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }

Is not it, I did not lie to you, from the return builder.toString () can be seen here, and the main flow of this code just concatenate strings only, nothing to say, if a string of small mosaic interested partners interested, you can go under study.

In the parse method SqlSourceBuilder.java code, the last returned is a StaticSqlSource object here is this object contains, we parsed the SQL statement is as shown in sql this points to the SQL statement is not originalSql points to the SQL template.

This article on the first end, due to space reasons, for the implementation of these SQL statements, it will be resolved in the next article. Thanks for the students to read, if there is an error, ask someone to correct me.

 

Published 41 original articles · won praise 8 · views 4257

Guess you like

Origin blog.csdn.net/zfy163520/article/details/97930261