Mybatis源码解读系列(一)-对查询标签的解析&创建BoundSql相关类分析

一、xml中的select查询节点

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.apache.ibatis.submitted.maptypehandler.Mapper">

    <select id="getUserXML" resultType="org.apache.ibatis.submitted.maptypehandler.User" parameterType="map">
         select * from users
         <where>
             id = #{id} and name = #{name}
         </where>
    </select>

</mapper>

​ 首先我们需要了解关于解析的一些类及一些基本内容,我们看下这个[select]节点,可以看到其包含属性attribute(例如id、resultType)、还可以包含子节点[where]、以及文本节点等。

我们现在来了解这个[select]节点是怎样被Mybatis处理封装的。

二、BaseBuilder类

public abstract class BaseBuilder {
  protected final Configuration configuration;
  protected final TypeAliasRegistry typeAliasRegistry;
  protected final TypeHandlerRegistry typeHandlerRegistry;

  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }
		......
  protected Boolean booleanValueOf(String value, Boolean defaultValue) {
    return value == null ? defaultValue : Boolean.valueOf(value);
  }

  protected Integer integerValueOf(String value, Integer defaultValue) {
    return value == null ? defaultValue : Integer.valueOf(value);
  }
      ......
  protected JdbcType resolveJdbcType(String alias) {
    if (alias == null) {
      return null;
    }
    try {
      return JdbcType.valueOf(alias);
    } catch (IllegalArgumentException e) {
      throw new BuilderException("Error resolving JdbcType. Cause: " + e, e);
    }
  }
      ......
  protected Object createInstance(String alias) {
    Class<?> clazz = resolveClass(alias);
    if (clazz == null) {
      return null;
    }
    try {
      return clazz.getDeclaredConstructor().newInstance();
    } catch (Exception e) {
      throw new BuilderException("Error creating instance. Cause: " + e, e);
    }
  }
      ......
    protected TypeHandler<?> resolveTypeHandler(Class<?> javaType, Class<? extends TypeHandler<?>>          typeHandlerType) {
    if (typeHandlerType == null) {
      return null;
    }
    // javaType ignored for injected handlers see issue #746 for full detail
    TypeHandler<?> handler = typeHandlerRegistry.getMappingTypeHandler(typeHandlerType);
    if (handler == null) {
      // not in registry, create a new one
      handler = typeHandlerRegistry.getInstance(javaType, typeHandlerType);
    }
    return handler;
  }
  protected <T> Class<? extends T> resolveAlias(String alias) {
    return typeAliasRegistry.resolveAlias(alias);
  }
}
public class TypeAliasRegistry {

  private final Map<String, Class<?>> typeAliases = new HashMap<>();
  public TypeAliasRegistry() {
    registerAlias("string", String.class);
    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    ......
    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    .....
    registerAlias("_integer", int.class);
    ......
    registerAlias("_long[]", long[].class);
    ......
    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    ......
    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    ......
  }
public enum JdbcType {
  ......
  FLOAT(Types.FLOAT),
  ......
  CHAR(Types.CHAR),
  VARCHAR(Types.VARCHAR),
  LONGVARCHAR(Types.LONGVARCHAR),
  ......
  CLOB(Types.CLOB),
  BOOLEAN(Types.BOOLEAN),
  ......
  public final int TYPE_CODE;
  private static Map<Integer,JdbcType> codeLookup = new HashMap<>();

  static {
    for (JdbcType type : JdbcType.values()) {
      codeLookup.put(type.TYPE_CODE, type);
    }
  }
    ......

​ 通过这些方法我们可以看到,这个类是提供了一些公共方法:

1、例如简单的booleanValueOf - 将string类型的布尔值转化为其原本的包装类型。

2、createInstance方法根据java类型的别名创建对应的对象,用的TypeAliasRegistry,其初始化的时候是会默认注入对应关系,例如[result column=“name” property=“name” javaType=“string”],这个映射,这里的javaType=“string"就可以通过别名string获取到String.class。这里我们可以注意到的一点是对于基本类型,例如float,如果我们写的是"float"其映射的基本类型的包装类Float.class,如果是”_float"则映射的是原生类型float.clas。

3、resolveJdbcType这个是根据jdbcType类型(字符类型的别名)获取对应的JdbcType,例如[result column=“name” property=“name” jdbcType=“INTEGER”]。

4、resolveTypeHandler通过typeHandlerType来从typeHandlerRegistry中获取对应类型的类型处理器对象。

三、XMLScriptBuilder(BaseBuilder的子类)

1、XMLScriptBuilder类结构

public class XMLScriptBuilder extends BaseBuilder {

  private final XNode context;
  private boolean isDynamic;
  private final Class<?> parameterType;
  private final Map<String, NodeHandler> nodeHandlerMap = new HashMap<>();
  public XMLScriptBuilder(Configuration configuration, XNode context) {
    this(configuration, context, null);
  }
  public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    initNodeHandlerMap();
  }
  private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
  }
  ......

​ 我们可以看到这个类是继承的BaseBuilder,这个类主要是来引导处理xml中的标签内容的(也可以处理注解中写的[scripte]这种),例如:

@Select({
  "<script>",
  "  select count(*) from users u where u.id in",
  "  <foreach item='item' index='index' collection='ids' open='(' separator=',' close=')'>",
  "    #{item}",
  "  </foreach>",
  "</script>"
})
Long getUserCountUsingList(List<Integer> ids);

2、其的成员变量:

1)、isDynamic

​ 这个是相对于sql来说的,对于mybatis来说是存在两种替换赋值的即"$()"、"#{}"这两种。

GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }
int end = text.indexOf(closeToken, offset);
   ......
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();
}
@Override
public String handleToken(String content) {
   this.isDynamic = true;
   return null;
}

​ 其是用这个GenericTokenParser去处理发现找文本中的内容是哪种填充方法(本文下面梳理对应调用的时候在具体分析)。

2)、parameterType

​ 即对应的初始类型。

3)、nodeHandlerMap

​ 这个我们看XMLScriptBuilder的构造函数通过initNodeHandlerMap方法对其的初始化,就明白其是构建对应子节点的节点处理器与节点名称的映射关系的,例如[foreach]就是用的ForEachHandler类处理的。

3、NodeHandler及SqlNode的遍历

​ 这个就是用来处理对应标签的,首先我们来看下xml的sql语句的节点

<select id="getUserXML" resultType="org.apache.ibatis.submitted.maptypehandler.User" parameterType="map">
     select * from users
     <where>
         id = #{id} and name = #{name}
     </where>
</select>

​ 我们来看下在这个[select]之间中有直接的文本节点内容,以及[where]节点(例如还看有[if]、[choose]这些),而这些在mybatis中都有一个接口用来描叙它

public interface SqlNode {
  boolean apply(DynamicContext context);
}

在这里插入图片描述

​ 可以看到对应内容有对应节点不同的SqlNode实现,在这些SqlNode实现中我们需要关注其中一个MixedSqlNode

public class MixedSqlNode implements SqlNode {
  private final List<SqlNode> contents;

  public MixedSqlNode(List<SqlNode> contents) {
    this.contents = contents;
  }

  @Override
  public boolean apply(DynamicContext context) {
    contents.forEach(node -> node.apply(context));
    return true;
  }
}

​ 可以看到该实现中主要是一个成员变量contents,这个其实就是用来放其他的SqlNode的,因为我们知道在一个例如[select]中可能会用到多种SqlNode,还有一个就是StaticTextSqlNode这个就是用来放静态的文本内容的.

private class WhereHandler implements NodeHandler {
	......
  @Override
  public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
    targetContents.add(where);
  }
}
private class SetHandler implements NodeHandler {
  ......
  @Override
  public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
    MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
    SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
    targetContents.add(set);
  }
}
protected MixedSqlNode parseDynamicTags(XNode node) {
  List<SqlNode> contents = new ArrayList<>();
  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 = nodeHandlerMap.get(nodeName);
      if (handler == null) {
        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
      }
      handler.handleNode(child, contents);
      isDynamic = true;
    }
  }
  return new MixedSqlNode(contents);
}

​ 可以看到这里会有一个for循环遍历解析的过程,通过

protected MixedSqlNode parseDynamicTags(XNode node) {
  List<SqlNode> contents = new ArrayList<>();
  ......
      contents.add(textSqlNode);
 ......
  return new MixedSqlNode(contents);
}

​ 这个这种循环构建赋值,将其他SqlNode都会添加到对应MixedSqlNode组中。例如上面的[select id=“getUserXML”]节点对应的SqlNode集就是

在这里插入图片描述

四、DynamicContext

​ 这个类就是用来存放各个SqlNode最终解析产生的sql文本参数内容

public class StaticTextSqlNode implements SqlNode {
  private final String text;
	......
  @Override
  public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
  }
}
public class DynamicContext {

  public static final String PARAMETER_OBJECT_KEY = "_parameter";
  public static final String DATABASE_ID_KEY = "_databaseId";
	......
  private final ContextMap bindings;
  private final StringJoiner sqlBuilder = new StringJoiner(" ");
  private int uniqueNumber = 0;
    ......
  public void appendSql(String sql) {
    sqlBuilder.add(sql);
  }

在这里插入图片描述

在这里插入图片描述

​ 例如这里就拼接StaticTextSqlNode中的sql文本内容,然后还有传入的参数

@Test
void shouldGetAUserFromXML() {
  try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    Mapper mapper = sqlSession.getMapper(Mapper.class);
    Map<String, Object> params = new HashMap<>();
    params.put("id", 1);
    params.put("name", "User1");
    Assertions.assertThrows(PersistenceException.class, () -> mapper.getUserXML(params));
  }
}

​ 我们看下一个WhereSqlNode(其继承TrimSqlNode)
在这里插入图片描述
​ 这里又会再次出发MixedSqlNode的变量,例如这里又会将中的静态内容再拼接到sqlBuilder中。
在这里插入图片描述

​ 最后就通过各个SqlNode的遍历处理拼接成了一条sql的文本内容。

五、LanguageDriver

public interface LanguageDriver {

  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);

  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}

​ 这个接口就是用于sql节点[select]的整个解析过程的,其有两个子类:XMLLanguageDriver、RawLanguageDriver(继承XMLLanguageDriver)。

1、XMLLanguageDriver

public class XMLLanguageDriver implements LanguageDriver {

  @Override
  public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
  }

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

  @Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    // issue #3
    if (script.startsWith("<script>")) {
      XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
      return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
    } else {
      // issue #127
      script = PropertyParser.parse(script, configuration.getVariables());
      TextSqlNode textSqlNode = new TextSqlNode(script);
      if (textSqlNode.isDynamic()) {
        return new DynamicSqlSource(configuration, textSqlNode);
      } else {
        return new RawSqlSource(configuration, script, parameterType);
      }
    }
  }
}
public SqlSource parseScriptNode() {
  MixedSqlNode rootSqlNode = parseDynamicTags(context);
  SqlSource sqlSource;
  if (isDynamic) {
    sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  } else {
    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  }
  return sqlSource;
}

​ 可以看到这里与前面的XMLScriptBuilder联系起来了(new XMLScriptBuilder(configuration, script, parameterType)),同时这里有两个createSqlSource方法上面表示的是被xml解析[select]节点使用的,下面的是被注解解析的时候使用的。同时在下面的createSqlSource方法会判断(script.startsWith(“script”)),这个是用来判断注解的写法是不是用xml中的节点的写法,例如前面的getUserCountUsingList方法@Select(…)中写的那样,如果是这种直接调用第一个createSqlSource方法使用xml中的写法遍历,如果不是,则直接使用TextSqlNode这种文本节点的方式去解析。

​ 这个script就是整个[select节点的内容([select id=“getUserXML”])表示了,然后这个parseScriptNode方法对parseDynamicTags方法调用,也承接了上面分析的parseDynamicTags对SqlNode的循环解析。

在这里插入图片描述

​ 同时根据是否是动态的来对应创建DynamicSqlSource/RawSqlSource。例如:

<select id="getUserXML" resultType="org.apache.ibatis.submitted.maptypehandler.User" parameterType="map">
    select * from users where id = ${id} and name = ${name}
</select>

在这里插入图片描述

<select id="getUserXML" resultType="org.apache.ibatis.submitted.maptypehandler.User" parameterType="map">
    select * from users where id = #{id} and name = #{name}
</select>

在这里插入图片描述

2、RawLanguageDriver

public class RawLanguageDriver extends XMLLanguageDriver {
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    SqlSource source = super.createSqlSource(configuration, script, parameterType);
    checkIsNotDynamic(source);
    return source;
  }
  @Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    SqlSource source = super.createSqlSource(configuration, script, parameterType);
    checkIsNotDynamic(source);
    return source;
  }
  private void checkIsNotDynamic(SqlSource source) {
    if (!RawSqlSource.class.equals(source.getClass())) {
      throw new BuilderException("Dynamic content is not allowed when using RAW language");
    }
  }
}

​ 我们可以看到这个RawLanguageDriver其都是直接调用父类(XMLLanguageDriver)的方法,再判断产生的SqlSource是否是RawSqlSource类型(checkIsNotDynamic(…)),这个一般是处理使用"${}"的写法。

六、SqlSource

public interface SqlSource {

  BoundSql getBoundSql(Object parameterObject);

}

​ 这个接口是用来初始BoundSql的,这个BoundSql是用存放待数据库引擎执行的一些参数。

1、BoundSql(以DynamicSqlSource的getBoundSql为例)

public class BoundSql {

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;......

​ 可以看到这个类的成员变量有直接的sql语句,以及用来填充参数。

在这里插入图片描述

2、SqlSource的子类及它们之间的关系

​ 然后其有四个子类:StaticSqlSource、RawSqlSource、DynamicSqlSource、ProviderSqlSource。这四个子类通过前面我们知道RawSqlSource与DynamicSqlSource的区别,然后其实在getBoundSql方法调用的内部这两个都会使用到StaticSqlSource来创建BoundSql。ProviderSqlSource这个是用于注解的如SelectProvider、InsertProvider等,对应于直接的@Select、@Delete这些注解。

private SqlSource buildSqlSource(Annotation annotation, Class<?> parameterType, LanguageDriver languageDriver,
    Method method) {
  if (annotation instanceof Select) {
    return buildSqlSourceFromStrings(((Select) annotation).value(), parameterType, languageDriver);
  } else if (annotation instanceof Update) {
    return buildSqlSourceFromStrings(((Update) annotation).value(), parameterType, languageDriver);
  } else if (annotation instanceof Insert) {
    return buildSqlSourceFromStrings(((Insert) annotation).value(), parameterType, languageDriver);
  } else if (annotation instanceof Delete) {
    return buildSqlSourceFromStrings(((Delete) annotation).value(), parameterType, languageDriver);
  } else if (annotation instanceof SelectKey) {
    return buildSqlSourceFromStrings(((SelectKey) annotation).statement(), parameterType, languageDriver);
  }
  return new ProviderSqlSource(assistant.getConfiguration(), annotation, type, method);
}

​ 这种Provider注解就是用来给你自己去产生Sql语句的内容的,其参数的内容对应的是我们前面讲过的XMLLanguageDriver createSqlSource方法的参数script的内容。

public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) 

例如其的使用:

@SelectProvider(type = StatementProvider.class, method = "provideSelect")
List<S> selectList(S param);
class StatementProvider {
  public String provideSelect(Object param) {
    StringBuilder sql = new StringBuilder("select * from ");
    if (param == null || param instanceof Person) {
      sql.append(" person ");
      if (param != null && ((Person) param).getId() != null) {
        sql.append(" where id = #{id}");
      }
    } else if (param instanceof Country) {
      sql.append(" country ");
      if (((Country) param).getId() != null) {
        sql.append(" where id = #{id}");
      }
    }
    sql.append(" order by id");
    return sql.toString();
  }

​ 所以其实这个ProviderSqlSource最后也是在使用RawSqlSource或DynamicSqlSource。现在我们来具体看下

3、StaticSqlSource

public class StaticSqlSource implements SqlSource {

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Configuration configuration;
  ......
  public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings)   {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.configuration = configuration;
  }
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }
}
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
  this.sql = sql;
  this.parameterMappings = parameterMappings;
  this.parameterObject = parameterObject;
  this.additionalParameters = new HashMap<>();
  this.metaParameters = configuration.newMetaObject(additionalParameters);
}

​ 我们可以看到其的getBoundSql方法很简单,就是完成BoundSql参数的初始化。

4、DynamicSqlSource

public class DynamicSqlSource implements SqlSource {

  private final Configuration configuration;
  private final SqlNode rootSqlNode;
	......
  @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);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }
}

​ 这个入参就是用来传到sql中的内容的。然后这个context(DynamicContext)、rootSqlNode.apply()(MixedSqlNode)、我们在前面有分析就不再具体分析了。然后再构建了一个SqlSerceBuilderd对象,后面调用sqlSourceParser.parse就会产生一个SqlSource(StaticSqlSource)对象(parse方法的具体解析到下面的SqlSourceBuilder类分析),然后再通过StaticSqlSource初始化构建一个BoundSql对象返回。下面我们先来简单分析下SqlSourceBuilder。

5、SqlSourceBuilder

public class SqlSourceBuilder extends BaseBuilder {

  private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

  public SqlSourceBuilder(Configuration configuration) {
    super(configuration);
  }
    ......

​ 我们可以看到其是继承我们前面梳理的BaseBuilder类,然后PARAMETER_PROPERTIES就包含了我们可能写在节点中的一些属性的key。例如我们这样写一个sql(id = #{id,jdbcType=INTEGER,javaType=int}),带上其的jdbcType、javaType。然后我们来看SqlSourceBuilder中的方法。

<select id="getUserXML" resultType="org.apache.ibatis.submitted.maptypehandler.User" parameterType="map">
    select * from users
    <where>
        <if test="id != null">
            id = #{id,jdbcType=INTEGER,javaType=int}
        </if>
        and name = #{name}
    </where>
</select>

1)、parse(SqlSourceBuilder)

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
  ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
  GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  String sql;
  if (configuration.isShrinkWhitespacesInSql()) {
    sql = parser.parse(removeExtraWhitespaces(originalSql));
  } else {
    sql = parser.parse(originalSql);
  }
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

​ 这个方法就是上面调用的sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()),其入参

在这里插入图片描述

​ 然后这里创建GenericTokenParser的时候传入的处理器是ParameterMappingTokenHandler。然后这里先判断isShrinkWhitespacesInSql,是否去掉一些多余的空格等,其就会移除这些(" \t\n\r\f"),默认为false。然后调用parser.parse解析为一个sql语句,这样就完成了DynamicSqlSource的getBoundSql方法中对StaticSqlSource的创建,然后就通过调用。

在这里插入图片描述

2)、parse(GenericTokenParser)

​ 这个就是用来解析sql文本中的参数的,并用?作为对应的占位符。这里关键会调用ParameterMappingTokenHandler的handleToken方法。

private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
@Override
public String handleToken(String content) {
  parameterMappings.add(buildParameterMapping(content));
  return "?";
}

在这里插入图片描述

private ParameterMapping buildParameterMapping(String content) {
  Map<String, String> propertiesMap = parseParameterMapping(content);
  String property = propertiesMap.get("property");
  ......
  return builder.build();
}

​ 这个buildParameterMapping方法主要可以分为3个部分。

​ 第一部分:对parseParameterMapping方法的调用,这个方法主要就是对context中的内容进行提取,然后以key-value键值对的形式放到Map中。

在这里插入图片描述

​ 第二部分:设置对应的propertyType(javaType)

String property = propertiesMap.get("property");
Class<?> propertyType;
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
  propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
  propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
  propertyType = java.sql.ResultSet.class;
} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
  propertyType = Object.class;
} else {
  MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
  if (metaClass.hasGetter(property)) {
    propertyType = metaClass.getGetterType(property);
  } else {
    propertyType = Object.class;
  }
}

​ 第三部分:构建ParameterMapping.Builder

ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
  String name = entry.getKey();
  String value = entry.getValue();
  if ("javaType".equals(name)) {
    javaType = resolveClass(value);
    builder.javaType(javaType);
  } else if ("jdbcType".equals(name)) {
    builder.jdbcType(resolveJdbcType(value));
  }
    ......
  } else if ("property".equals(name)) {
    // Do Nothing
  } else if ("expression".equals(name)) {
    throw new BuilderException("Expression based parameters are not supported yet");
  } else {
    throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + PARAMETER_PROPERTIES);
  }
}
if (typeHandlerAlias != null) {
  builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();

​ 这里就是遍历propertiesMap,将对应内容设置到到Builder中,再返回为ParameterMapping。

在这里插入图片描述

3)、ParameterExpression(对content内容的解析)

private Map<String, String> parseParameterMapping(String content) {
    try {
      return new ParameterExpression(content);
    } catch (BuilderException ex) {
      throw ex;
    } catch (Exception ex) {
      throw new BuilderException("Parsing error was found in mapping #{" + content + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
    }
  }
}
public class ParameterExpression extends HashMap<String, String> {
    .......
public ParameterExpression(String expression) {
    parse(expression);
}
private void parse(String expression) {
  int p = skipWS(expression, 0);
  if (expression.charAt(p) == '(') {
    expression(expression, p + 1);
  } else {
    property(expression, p);
  }
}
private int skipWS(String expression, int p) {
    for (int i = p; i < expression.length(); i++) {
      if (expression.charAt(i) > 0x20) {
        return i;
      }
    }
    return expression.length();
}

解析就是从这里开始,这个skipWS就是找到开始的位置,如果expression中有"("这种就是用表达式去解析,这里我们跳过,按一般使用进入property:

private void property(String expression, int left) {
  if (left < expression.length()) {
    int right = skipUntil(expression, left, ",:");
    put("property", trimmedStr(expression, left, right));
    jdbcTypeOpt(expression, right);
  }
}

​ 这里就是提取对应内容实现是提取属性,例如以前面的content字符串为例,这里trimmedStr提取的就是"id",这里就会put一个property-id的键值对。这里之所以会以",:“形式,多了一个”:",是因为有一个用法能直接设置jdbcType的内容

@Test
void simplePropertyWithOldStyleJdbcTypeAndAttributes() {
  Map<String, String> result = new ParameterExpression("id:VARCHAR, attr1=val1, attr2=val2");
  Assertions.assertEquals(4, result.size());
  Assertions.assertEquals("id", result.get("property"));
  Assertions.assertEquals("VARCHAR", result.get("jdbcType"));
  Assertions.assertEquals("val1", result.get("attr1"));
  Assertions.assertEquals("val2", result.get("attr2"));
}
private void jdbcTypeOpt(String expression, int p) {
  p = skipWS(expression, p);
  if (p < expression.length()) {
    if (expression.charAt(p) == ':') {
      jdbcType(expression, p + 1);
    } else if (expression.charAt(p) == ',') {
      option(expression, p + 1);
    } else {
      throw new BuilderException("Parsing error in {" + expression + "} in position " + p);
    }
  }
}
private void jdbcType(String expression, int p) {
    int left = skipWS(expression, p);
    int right = skipUntil(expression, left, ",");
    if (right > left) {
      put("jdbcType", trimmedStr(expression, left, right));
    } else {
      throw new BuilderException("Parsing error in {" + expression + "} in position " + p);
    }
    option(expression, right + 1);
}

​ 例如这里首先看是不是’:’,是就调用jdbcType,可以看到其设置的key是"jdbcType",其他的就通过option方法去提取key-value

private void option(String expression, int p) {
  int left = skipWS(expression, p);
  if (left < expression.length()) {
    int right = skipUntil(expression, left, "=");
    String name = trimmedStr(expression, left, right);
    left = right + 1;
    right = skipUntil(expression, left, ",");
    String value = trimmedStr(expression, left, right);
    put(name, value);
    option(expression, right + 1);
  }
}

​ 这里是有一个自调用一直解析到最后。

6、RawSqlSource

public class RawSqlSource implements SqlSource {

  private final SqlSource sqlSource;

  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }
  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }
  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
  }
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
  }
}

​ 我们看到RawSqlSource其与前面的DynaticSqlSource大体差别不大,但在通过SqlSourceBuilder构建StaticSqlSource的时候是在其的初始化方法的时候。

7、ProviderSqlSource

public class ProviderSqlSource implements SqlSource {
  ......
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    SqlSource sqlSource = createSqlSource(parameterObject);
    return sqlSource.getBoundSql(parameterObject);
  }

  private SqlSource createSqlSource(Object parameterObject) {
    try {
      String sql;
          ......
          sql = invokeProviderMethod(parameterObject);
         ......
      } else {
        ......
      }
      Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
      return languageDriver.createSqlSource(configuration, sql, parameterType);
    } catch (BuilderException e) {
      ......
    }
  }
  ......
  private String invokeProviderMethod(Object... args) throws Exception {
    Object targetObject = null;
    if (!Modifier.isStatic(providerMethod.getModifiers())) {
      targetObject = providerType.getDeclaredConstructor().newInstance();
    }
    CharSequence sql = (CharSequence) providerMethod.invoke(targetObject, args);
    return sql != null ? sql.toString() : null;
  }
  ........
}

​ 这个类其实比较复杂,例如由于其面向注解,所以涉及从注解中获取执行类及方法并进行对象创建,但我们都省略这些。可以看到其主要逻辑是通过执行对应方法来获取sql,再调用languageDriver.createSqlSource(configuration, sql, parameterType),来产生StaticSqlSource,这里就加入到本文最开始的梳理的XMLLanguageDriver的createSqlSource方法了。

七、TypeHandlerRegistry

public final class TypeHandlerRegistry {

  private final Map<JdbcType, TypeHandler<?>>  jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
  private final TypeHandler<Object> unknownTypeHandler;
  private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
    ......
  public TypeHandlerRegistry(Configuration configuration) {
    this.unknownTypeHandler = new UnknownTypeHandler(configuration);

    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    ......
    register(LocalDateTime.class, new LocalDateTimeTypeHandler());
    register(LocalDate.class, new LocalDateTypeHandler());
    register(LocalTime.class, new LocalTimeTypeHandler());
    ......
  }
    ......
   private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
        register(javaType, handledJdbcType, typeHandler);
      }
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else {
      register(javaType, null, typeHandler);
    }
  }
    ......
  private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
      if (map == null || map == NULL_TYPE_HANDLER_MAP) {
        map = new HashMap<>();
      }
      map.put(jdbcType, handler);
      typeHandlerMap.put(javaType, map);
    }
    allTypeHandlersMap.put(handler.getClass(), handler);
  }

​ 这个类是用来处理类型的,例如我们再Mapper的xml文件中可以设置列字段的java类型,数据库类型。例如这里的jdbcTypeHandlerMap:key就是数据库类型,然后TypeHandler就是用来处理对应类型的。

​ 这里有一个unknownTypeHandler的类型处理器,其参数化的时候传入的是Configuration类的typeHandlerRegistry变量,也就是直接用的是当前这个TypeHandlerRegistry。

​ 然后可以看到TypeHandlerRegistry会注册默认的处理类型及对应的类型处理器。然后在register方法中还有一个分支,会判断在类型处理器上面有没有@MappedJdbcTypes注解。如果有注解,则最终注册是在typeHandlerMap变量里,也就是处理为一种可以通过java类型获取对应的数据库类型&类型处理器。

@MappedTypes(String.class)
@MappedJdbcTypes(value = { JdbcType.CHAR, JdbcType.VARCHAR }, includeNullJdbcType = true)
public class StringTrimmingTypeHandler implements TypeHandler<String> {

​ 我们可以看到,这个类型处理器其实就是用java的String类型去处理数据库的CHAR及VARCHAR类型。
dler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap<>();
}
map.put(jdbcType, handler);
typeHandlerMap.put(javaType, map);
}
allTypeHandlersMap.put(handler.getClass(), handler);
}


​    这个类是用来处理类型的,例如我们再Mapper的xml文件中可以设置列字段的java类型,数据库类型。例如这里的jdbcTypeHandlerMap:key就是数据库类型,然后TypeHandler就是用来处理对应类型的。

​    这里有一个unknownTypeHandler的类型处理器,其参数化的时候传入的是Configuration类的typeHandlerRegistry变量,也就是直接用的是当前这个TypeHandlerRegistry。

​    然后可以看到TypeHandlerRegistry会注册默认的处理类型及对应的类型处理器。然后在register方法中还有一个分支,会判断在类型处理器上面有没有@MappedJdbcTypes注解。如果有注解,则最终注册是在typeHandlerMap变量里,也就是处理为一种可以通过java类型获取对应的数据库类型&类型处理器。

```java
@MappedTypes(String.class)
@MappedJdbcTypes(value = { JdbcType.CHAR, JdbcType.VARCHAR }, includeNullJdbcType = true)
public class StringTrimmingTypeHandler implements TypeHandler<String> {

​ 我们可以看到,这个类型处理器其实就是用java的String类型去处理数据库的CHAR及VARCHAR类型。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值