maybatis源码分析(三)——mybatis如何获取sql语句?

 

 

在mybatis中sql的来源一般分位两种方式:

                                 详细总结图

1.写在xml文件中,如:

2.通过注解写入sql,如:

在此文中我们将研究mybatis是如何获取他们,并把他们放在哪。

从上一篇博客(maybatis源码分析(二)——mybatis mapper的注册流程_zhaoliubao1的博客-CSDN博客)我们可以知道,mybatis首先会读取配置文件,并最终构建出mapper接口的代理工厂类,此时我们的mapper接口的相关信息已经被获取,那么接下来我们就要具体去寻找mapper接口对应的方法的sql。有两种情况,一种是写在xml文件中,另一种写通过注解的方式去记录将要执行的sql。

我们先研究mybatis是如何解析注解中携带的sql语句:

源码如下:

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<>(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);
      //------解析sql----//
      parser.parse();
     //------解析sql----//
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

 其实我们可以看到,解析sql的时候我们需要传入该类的信息(文件路径)其实就是mapper注册的key值。

接下来便是真正解析sql了:

public void parse() {
  String resource = type.toString();
  //防止重复解析
  if (!configuration.isResourceLoaded(resource)) {
    //通过xml去解析sql
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    parseCache();
    parseCacheRef();
    //获取改接口的所有方法 遍历解析sql 组装ResultMap
    for (Method method : type.getMethods()) {
      if (!canHaveStatement(method)) {
        continue;
      }
      if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
          && method.getAnnotation(ResultMap.class) == null) {
        parseResultMap(method);
      }
      try {
        //解析sql的入口
        parseStatement(method);
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  parsePendingMethods();
}

先看注解解析入口代码:

//注解解析sql的入口
parseStatement(method);

拨开云雾我们终于看到了SqlSource 这便是sql源了

void parseStatement(Method method) {
  //获取该方法的返回值
  final Class<?> parameterTypeClass = getParameterType(method);
  final LanguageDriver languageDriver = getLanguageDriver(method);

  getAnnotationWrapper(method, true, statementAnnotationTypes).ifPresent(statementAnnotation -> {
    //这里便是最终得到的sql源代码了
    final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);
    final SqlCommandType sqlCommandType = statementAnnotation.getSqlCommandType();
    final Options options = getAnnotationWrapper(method, false, Options.class).map(x -> (Options)x.getAnnotation()).orElse(null);
    final String mappedStatementId = type.getName() + "." + method.getName();

    final KeyGenerator keyGenerator;
    String keyProperty = null;
    String keyColumn = null;
.....
}

首先我们需要先了解一下这个方法:

private Optional<AnnotationWrapper> getAnnotationWrapper(Method method, boolean errorIfNoMatch,
    Collection<Class<? extends Annotation>> targetTypes) {
  String databaseId = configuration.getDatabaseId();
  Map<String, AnnotationWrapper> statementAnnotations = targetTypes.stream()
      .flatMap(x -> Arrays.stream(method.getAnnotationsByType(x))).map(AnnotationWrapper::new)
      .collect(Collectors.toMap(AnnotationWrapper::getDatabaseId, x -> x, (existing, duplicate) -> {
        throw new BuilderException(String.format("Detected conflicting annotations '%s' and '%s' on '%s'.",
            existing.getAnnotation(), duplicate.getAnnotation(),
            method.getDeclaringClass().getName() + "." + method.getName()));
      }));
......
}

这段代码的意思就是:将某个方法的注解何预定的注解进行匹配,并返回。 

接下来我们将研究他是如何获取并赋值给 SqlSource对象的,也就是:

final SqlSource sqlSource = buildSqlSource(statementAnnotation.getAnnotation(), parameterTypeClass, languageDriver, method);

我们继续深入可以发现:

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

通过先前getAnnotationWrapper我们已经得到了某个方法对应的注解,那在此处我们就可以知道这个注解的具体信息

我们看其中一段代码:

private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass,
    LanguageDriver languageDriver) {
  return languageDriver.createSqlSource(configuration, String.join(" ", strings).trim(), parameterTypeClass);
}

继续进入:

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

通过我们前面的讲解 我们已经知道了 sql来源 ,所以如果我么的来源是注解并且没有<script>标签那么他将会走else逻辑,比如我们现在拿到了一条这样的sql:

 他究竟会怎么做呢?

我们进入他的解析器一探究竟:

public static String parse(String string, Properties variables) {
  VariableTokenHandler handler = new VariableTokenHandler(variables);
  GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
  return parser.parse(string);
}

我们可以看到 这个解析器主要是用于处理含有$符号的sql,含有#号的sql是通RawSqlSource

类来处理,所以我们将会看到:

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

继续进入将会看到:

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

 

通过我们的通用解析器,最终我们将会得到:

 

 自此我们将得到了每个statement的目标sql

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值