mybatis sqlSource時序圖(二)

上圖是sqlsource在mybatis中建立的時序圖;node

如下會經過源碼的方式將時序圖進行深刻的說明;sql

1.XMLStatementBuilder對xxxMapper.xml中對每一個CURD進行解析成MappedStatement對象express

如:apache

<select id="selectRawWithInclude" resultType="Name" lang="raw">
    SELECT firstName, lastName
    <include refid="include"/>
    WHERE lastName LIKE #{name}
  </select>

lang屬性指定該MapperStatement中的sqlSource屬性是由哪類LanguageDriver進行markdown

public interface LanguageDriver {

  /**
   * 建立ParameterHandler,將入參的數據傳入jdbc statement ex:PrepareStatement中的 '?'
   */
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

  /**
   * 經過解析xml中的curd node數據建立SqlSource
   */
  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);

  /**
   * 經過解析Mapper interface method上的註解數據建立SqlSource
   */
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);

}



lang=xml -> XMLLanguageDriver:建立動態sql的LanguageDriver
lang=raw -> RawLanguageDriver:建立靜態sql的LanguageDriver

在解析MapperStatement時,經過typeAliasRegistry解析lang值獲取LanguageDriver;
  private LanguageDriver getLanguageDriver(String lang) {
    Class<? extends LanguageDriver> langClass = null;
    if (lang != null) {
      langClass = resolveClass(lang);
    }
    return configuration.getLanguageDriver(langClass);
  }

===
  protected <T> Class<? extends T> resolveClass(String alias) {
    if (alias == null) {
      return null;
    }
    try {
      return resolveAlias(alias);
    } catch (Exception e) {
      throw new BuilderException("Error resolving class. Cause: " + e, e);
    }
  }
===
  protected <T> Class<? extends T> resolveAlias(String alias) {
    return typeAliasRegistry.resolveAlias(alias);
  }

===
而後由Configuration中的languageRegistry獲取LanguageDriver對象
  public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
    if (langClass == null) {
      return languageRegistry.getDefaultDriver();
    }
    languageRegistry.register(langClass);
    return languageRegistry.getDriver(langClass);
  }

languageRegistry初始化
    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

2.調用XMLScriptBuilder解析sql語句,將trim/where/set/foreach/if/choose/when/otherwise/bind標籤解析成相應的SqlNode對象,最後由MixedSqlNode進行封裝;mybatis

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;
  }
}

將sql語句中的標籤經過實現NodeHandler接口的類生成SqlNode對象;由MixedSqlNode對象進行封裝;app

XMLScriptBuilder類很精簡,初始化sql 標籤的map;定義生成SqlNode的NodeHandler接口ide

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());

private interface NodeHandler {
  void handleNode(XNode nodeToHandle, List<SqlNode> targetContents);
}

3.將sql語句中的標籤進行解析時,判斷是不是動態語句函數

XMLScriptBuilder#
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;
}

若是是動態的則返回DynamicSqlSource,反之則返回RawSqlSource學習

4/5/6.MappStatement對象中包含屬性SqlSource;

7.在調用SqlSession.select方法時,調用MappedStatement.getBoundSql(parameterObject)獲取BoundSql對象

MappedStatement#
public BoundSql getBoundSql(Object parameterObject) {
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  ...
  return boundSql;
}
DynamicSqlSource#
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;
}

1.建立DynamicContext對象
2.調用rootSqlNode.apply(context)方法,將SqlNode對象的apply方法,將sql動態語句轉成靜態語句
3.將解析後的sql、入參類型、context.getBindings()值傳入SqlSourceBuilder,進行解析返回StaticSqlSource
4.傳入入參對象,返回boundSql

8.調用SqlSourceBuilder.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 = parser.parse(originalSql);
  return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

針對#{id, typeHandler=org.apache.ibatis.submitted.uuid_test.UUIDTypeHandler}這種狀況,
1.將#{}的內容轉化爲ParameterExpression對象
2.將additionalParameters中的值轉換爲MetaObject;經過屬性名獲取屬性的類型
3.經過根據類型判斷是否有類型處理器
4.將對象中的內容進行解析,轉換爲ParameterMapping

經過以上方式,將動態sql語句中的#{}值轉化爲ParameterMapping;進行生成StaticSqlSource對象

9/10.調用StaticSqlSource.getBoundSql方法;

public class StaticSqlSource implements SqlSource {
  ...
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }

}

以上流程進行總結:

1.經過lang屬性從languageRegistry中獲取LanguageDriver,languageDriver經過xml or annotation解析獲取DynamicSqlSource or RawSqlSource

2.以DynamicSqlSource爲例,對sql語句中對標籤轉化爲SqlNode對象,而後進行解析,將解析後的數據存放於DynamicContext

3.根據sql語句中的#{}值解析成ParameterMapping,並以configuration、sql(解析後的靜態語句)、值的映射(ParameterMapping)組裝StaticSqlSource

4.最後轉化爲BoundSql對象

================================================================

在學習的過程當中,DynamicContext比較有意思,如今說一下這個類

DynamicContext類是在執行多個sqlNode的入參,將每一個SqlNode.apply方法執行完的數據存入於DynamicContext;

DynamicContext的構造函數
public DynamicContext(Configuration configuration, Object parameterObject) {
  if (parameterObject != null && !(parameterObject instanceof Map)) {
    MetaObject metaObject = configuration.newMetaObject(parameterObject);
    boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
    bindings = new ContextMap(metaObject, existsTypeHandler);
  } else {
    bindings = new ContextMap(null, false);
  }
  bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
  bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}

將解析過程當中產生的屬性(如:ForEachSqlNode)存放於bindings,bindings繼承於HashMap
並且給bindings注入了propertyAccessor

static {
  OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());
}

當獲取contextMap中當元素時,會進入到ContextAccessor中先進行獲取,如獲取屬性的切面操做,對執行方法也是能夠的,注入methodAccessor

static class ContextAccessor implements PropertyAccessor {

  @Override
  public Object getProperty(Map context, Object target, Object name) {
    Map map = (Map) target;

    Object result = map.get(name);
    if (map.containsKey(name) || result != null) {
      return result;
    }

    Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
    if (parameterObject instanceof Map) {
      return ((Map)parameterObject).get(name);
    }

    return null;
  }
  ...
}

獲取方法

public static Object getValue(String expression, Object root) {
  try {
    Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
    return Ognl.getValue(parseExpression(expression), context, root);
  } catch (OgnlException e) {
    throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
  }
}

建立OgnlContext,將expression進行解析成property,而後會調用到自定義的PropertyAccessor中

Ognl其實主要功能是對集合、類對象屬性表達式的解析,如:

ContextMap bindings = new ContextMap(null, false);
OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());

bindings.put("xxx", "zzz");
bindings.put("yyy", "aaa");

OgnlContext context = new OgnlContext(null,null,new DefaultMemberAccess(true));
context.put("bindings", bindings);

Object obj = Ognl.getValue("#bindings.xxx", context, context.getRoot());
System.out.println(obj); // output: "zzz"

方法也是如此,當注入類MethodAccessor時,當經過表達式「bindings.put()」,也會執行MethodAccessor關於put方法的切面

相關文章
相關標籤/搜索