精通Mybatis之动态sql全流程解析

前言

小编的精通mybatis之讲解就快结束了,希望大家坚持不懈坚持到底了。好了进入今天的正题,动态sql全流程。

动态Sql定义

定义:每次构建sql脚本时,根据预先编写的脚本以及参数动态构建可执行的sql。
动态SQL是MyBatis 强大功能之一,他免除了在JAVA代码中拼装SQL字符串麻烦,同时保留了我们对SQL的自主控制,更方便进行SQL性能优化改造。这也是大部分的编程伙伴喜欢用mybatis的原因。
对动态sql的使用大家应该很熟悉了吧,如果有需要大家可以看官网使用动态 SQL。首先小编带大家看下sql脚本元素:

在这里插入图片描述
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类就上面这些了。其实官网里面还有bind和接口注解方式的script。
那小编带大家先了解一下OGNL的表达式吧。

OGNL表达式

OGNL全称是对象导航图语言(Object Graph Navigation Language)是一种JAVA表达示语言,可以方便的存取对象属和方法,已用于逻辑判断。其支持以下特性:

  • 获取属性属性值,以及子属性值进行逻辑计算
    id != null || autho.name != null
  • 表达示中可直接调用方法,(如果是无参方法,可以省略括号)
    ! comments.isEmpty && comments.get(0) != null
  • 通过下标访问数组或集合
    comments[0].id != null
    遍历集合
    Iterable<?> comments = evaluator.evaluateIterable(“comments”, blog);

接下来小编用代码示例演示一下
这边使用了官网的示例,Blog ,commit,user三个对象

public class OgnlTest {

    @Test
    public void ognlExpressionTest() {
        ExpressionEvaluator expressionEvaluator = new ExpressionEvaluator();
        Blog blog = new Blog();
        blog.setId(1);
        User user = new User();
        user.setId(2);
        blog.setAuthor(user);
        List<Comment> commentList = new ArrayList<>();
        Comment comment = new Comment();
        comment.setBody("123");
        commentList.add(comment);
        blog.setComments(commentList);
		//这里author如果为空的话author.id肯定会报错
        boolean hasAuthor = expressionEvaluator.evaluateBoolean("id !=null && author.id!=null", blog);
        System.out.println(hasAuthor);
        boolean hasComments = expressionEvaluator.evaluateBoolean("comments !=null && !comments.isEmpty", blog);
        System.out.println(hasComments);

        boolean hasCommentBody = expressionEvaluator.evaluateBoolean("comments !=null && comments.get(0).body!=null", blog);
        System.out.println(hasCommentBody);
        boolean hasCommentBody2 = expressionEvaluator.evaluateBoolean("comments !=null && comments[0].body!=null", blog);
        System.out.println(hasCommentBody2);

        Iterable<?> comments = expressionEvaluator.evaluateIterable("comments", blog);
        for (Object o : comments) {
            System.out.println(o);
        }
    }
}

测试结果:

true
true
true
true
org.coderead.mybatis.bean.Comment@5b1d2887

SqlSource解析过程

说完了OGNL表达式,咱们来说一下sql的解析过程,即从数据源到我们可以执行的sql。SqlSource是sql的数据源,他可以通过注解的方式或xml方式得到对应的源。右边的BoundSql就是可执行sql的所有东西。StatementHandler就是根据他去执行sql的(当然获取他的时候还包装了一层:MappedStatement),待会儿小编会带大家看他的源码,一看就明白了。
在这里插入图片描述
小编稍微对sqlSource的实现类讲解一下:

  1. ProviderSqlSource :第三方法SQL源,每次获取SQL都会基于参数动态创建静态数据源,然后在创建BoundSql
  2. DynamicSqlSource:动态SQL源包含了SQL脚本,每次获取SQL都会基于参数以及脚本,动态创建创建BoundSql
  3. RawSqlSource:不包含任何动态元素,原生文本的SQL。但这个SQL是不能直接执行的,需要转换成BoundSql
  4. StaticSqlSource:包含可执行的SQL,以及参数映射,可直接生成BoundSql。前面三个数据源都要先创建StaticSqlSource然后才创建BoundSql。

因为第三方很少涉及,一般我们只是使用静态或动态,静态很简单直接将#{}变成?,然后将参数值设置进去即可(这边在变成问号的时候,参数值映射也是一一对应的,有兴趣的小伙伴可以去看下源码)。如:select * from user where user_id =#{userId},所以小编着重讲一下动态sql源的解析过程。

动态Sql源解析

先看下动态sql源的解析流程。
在这里插入图片描述
看到这儿大家是不是很懵,这个小编是根据源码来写的,首先还是得让大家知道什么是SqlNode,以及我们的动态sql是如何和SqlNode建立起关系的。

在这里插入图片描述

SqlNode这里使用了解释器模式,小编这里简单的解释一下这个设计模式。
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
简单介绍
意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
主要解决:对于一些固定文法构建一个解释句子的解释器。
何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
如何解决:构建语法树,定义终结符与非终结符。
关键代码:构建环境类,包含解释器之外的一些全局信息,一般是 HashMap。

SqlNode主要是来解析Mybatis中的Sql脚本元素,之后将解析完毕sql添加到DynamicContext中去。小编简单说下各个SqlNode的作用:

  1. SqlNode是总接口只有一个方法:apply(DynamicContext context),作用如上面小编所讲,各个sqlNode处理完对应的逻辑然后将对应sql添加到DynamicContext
  2. MixedSqlNode包含多个子sqlNode,是个list然后循环调用子节点的逻辑
  3. ChooseSqlNode,IfSqlNode,ForEachSqlNode,TrimSqlNode这些节点就是来处理对应的sql脚本元素
  4. StaticTextSqlNode为静态脚本node,直接拼接的是静态脚本,如:select * from user
  5. TextSqlNode为文本脚本node,他主要是用来替换${}占位符的,直接替换成文本如:select * from ${table_name}

题外话:这边小编想起了一个mybatis的面试题,说${}与#{}替换符的区别,其实结论大家都知道,但是具体实现可能没有真正的看过,有兴趣的小伙伴可以看下。结论是很简单且正确的,但求证的却很少

这些SqlNode是怎样的数据结构才能解析我们的动态sql呢,看过解释器模式就知道他其实构建语法树,怎么构建的看下图:

在这里插入图片描述
脚本之间是呈现嵌套关系的。比如if元素中会包含一个MixedSqlNode ,而MixedSqlNode下又会包含1至1至多个其它节点。最后组成一课脚本语法树。如上图左边的SQL元素组成右边的语法树。在节点最底层一定是一个StaticTextNode或 TextNode。
这就是xm中的sqll脚本解析变成语法树的结构。这边小编不一一讲各种元素,这里挑选几个讲一下

if、where、foreach

模拟if,where解析过程:

@Test
    public void sqlNodeTest(){
        Company company = new Company();
        company.setId(1L);
        company.setCompanyName("伟大的公司");
        DynamicContext dynamicContext = new DynamicContext(configuration,company);
        StaticTextSqlNode staticTextSqlNode = new StaticTextSqlNode("select * from company");
        staticTextSqlNode.apply(dynamicContext);

        IfSqlNode ifIdSqlNode = new IfSqlNode(new StaticTextSqlNode("id = #{id}"),"id != null");
        IfSqlNode ifNameSqlNode = new IfSqlNode(new StaticTextSqlNode(" and company_name = #{companyName}"),"companyName != null");
        MixedSqlNode mixedSqlNode = new MixedSqlNode(Arrays.asList(ifIdSqlNode,ifNameSqlNode));
        WhereSqlNode whereSqlNode= new WhereSqlNode(configuration,mixedSqlNode);
        whereSqlNode.apply(dynamicContext);
        System.out.println(dynamicContext.getSql());
    }

测试结果:
在这里插入图片描述
这边大家有没有发现,if其实不需要MixedSqlNode,包装一个StaticTextSqlNode即可,为什么小编在上面的脚本树中包含的是MixedSqlNode,这是因为if里面还可以加if所以里面需要包装使用MixedSqlNode,在mybatis实际过程中也是如此。

froeach

@Test
    public void foreachNodeTest(){
        Map<String,Object> paramMap = new HashMap<>(1);
        List<Long> idList = Arrays.asList(1L,2L,3L);
        paramMap.put("idList",idList);
        DynamicContext dynamicContext = new DynamicContext(configuration,paramMap);
        StaticTextSqlNode staticTextSqlNode = new StaticTextSqlNode("select * from company where id in");
        ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration,
                new MixedSqlNode(Arrays.asList(new StaticTextSqlNode("#{item}"))),"idList","index","item","(",")",",");
        staticTextSqlNode.apply(dynamicContext);
        forEachSqlNode.apply(dynamicContext);
        System.out.println(dynamicContext.getSql());
    }

测试结果:
在这里插入图片描述
看到这个参数的替换是不是不一样啊。这边小编提个问,为什么List的参数小编要转成map参数传进去?
如果不了解的话建议看下精通Mybatis之Jdbc处理器StatementHandler中的参数转换。

小结

其实真正的解析也差不多,小编只是将他们拆开来了,实际过程根据xml里面的sql语句分段然后使用SqlNode拼装成脚本结构树,顶层只有MixedSqlNode 就是根节点,然后在执行的时候根据结构树变成sql。稍微有点区别的是,sqlNode还会进行一次包装。

动态标签xml解析过程

上面是小编是在底层拆开揉碎了展开的,那从用户角度,咱们编写好xml他是如何解析的呢,其实不难,听小编慢慢道来,SqlSource 是基于XML解析而来,解析的底层是使用Dom4j 把XML解析成一个个子节点,在通过 XMLScriptBuilder 遍历这些子节点最后生成对应的Sql源。其解析流程如下图:
在这里插入图片描述
nodeHandler 类图:
在这里插入图片描述

源码阅读:
小编编写了稍微复杂的xml的sql然后进行debug调试,测试xml的sql如下:

<?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="mapper.EmployeeMapper">

    <resultMap id="employMap" type="entity.Employee" autoMapping="true"/>
    <select id="selectByIdListAndName" resultType="collection" resultMap="employMap">
        select * from employee
        <where>
            <if test="idList != null and !idList.isEmpty">
                id in
                <foreach collection="idList" separator="," index="index" item="item" open="(" close=")">
                    #{item}
                </foreach>
            </if>

            <if test="name != null">
                name = #{name}
            </if>
        </where>
    </select>
</mapper>

XMLScriptBuilder源码(太多了小编捡一些重点):

//构造方法,其中context 就是上面的<select>标签的所有内容,parameterType为属性值的class类型
public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
    super(configuration);
    this.context = context;
    this.parameterType = parameterType;
    initNodeHandlerMap();
  }
  //初始化NodeHandlerMap,处理各脚本元素的handler,各节点最后是相应的sqlNode
  //然后添加到rootNode的contexts列表中(rootNode 类型是MixedSqlNode,
  //而contexts为MixedSqlNode中的属性 类型为List<SqlNode>)  
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());
  }
//开始解析
  public SqlSource parseScriptNode() {
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
       //如果是动态的就包装成动态的sqlsource
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
     //否则就是静态sqlSource
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }
  
  protected MixedSqlNode parseDynamicTags(XNode node) {
    //创建sqlNode子节点树
    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);
        //判断是否是脚本元素 动态sql
        if (textSqlNode.isDynamic()) {
          contents.add(textSqlNode);
          isDynamic = true;
        } else {
          contents.add(new StaticTextSqlNode(data));
        }
        //如果是元素node
      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
      	//取出node的名称
        String nodeName = child.getNode().getNodeName();
        //更加元素node取出对应的NodeHandler
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        //根据对应的nodeHandler处理对应的node节点
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    //根节点始终为MixedSqlNode
    return new MixedSqlNode(contents);
  }

NodeHandler源码:这是XMLScriptBuilder的内部接口和内部实现(下面比较简单,不停递归调用最后扔进相应的sqlNode即可)

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

  private class BindHandler implements NodeHandler {
    public BindHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      final String name = nodeToHandle.getStringAttribute("name");
      final String expression = nodeToHandle.getStringAttribute("value");
      final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
      targetContents.add(node);
    }
  }

  private class TrimHandler implements NodeHandler {
    public TrimHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String prefix = nodeToHandle.getStringAttribute("prefix");
      String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
      String suffix = nodeToHandle.getStringAttribute("suffix");
      String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
      TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
      targetContents.add(trim);
    }
  }

  private class WhereHandler implements NodeHandler {
    public WhereHandler() {
      // Prevent Synthetic Access
    }

    @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 {
    public SetHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
      targetContents.add(set);
    }
  }

  private class ForEachHandler implements NodeHandler {
    public ForEachHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String collection = nodeToHandle.getStringAttribute("collection");
      String item = nodeToHandle.getStringAttribute("item");
      String index = nodeToHandle.getStringAttribute("index");
      String open = nodeToHandle.getStringAttribute("open");
      String close = nodeToHandle.getStringAttribute("close");
      String separator = nodeToHandle.getStringAttribute("separator");
      ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);
      targetContents.add(forEachSqlNode);
    }
  }

  private class IfHandler implements NodeHandler {
    public IfHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
  }

  private class OtherwiseHandler implements NodeHandler {
    public OtherwiseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      targetContents.add(mixedSqlNode);
    }
  }

  private class ChooseHandler implements NodeHandler {
    public ChooseHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      List<SqlNode> whenSqlNodes = new ArrayList<>();
      List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
      handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
      SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
      ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
      targetContents.add(chooseSqlNode);
    }

    private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes, List<SqlNode> defaultSqlNodes) {
      List<XNode> children = chooseSqlNode.getChildren();
      for (XNode child : children) {
        String nodeName = child.getNode().getNodeName();
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        if (handler instanceof IfHandler) {
          handler.handleNode(child, ifSqlNodes);
        } else if (handler instanceof OtherwiseHandler) {
          handler.handleNode(child, defaultSqlNodes);
        }
      }
    }

    private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
      SqlNode defaultSqlNode = null;
      if (defaultSqlNodes.size() == 1) {
        defaultSqlNode = defaultSqlNodes.get(0);
      } else if (defaultSqlNodes.size() > 1) {
        throw new BuilderException("Too many default (otherwise) elements in choose statement.");
      }
      return defaultSqlNode;
    }
  }

这里小编将上面xml解析完的树结构展示一下:
在这里插入图片描述
where节点比较复杂
在这里插入图片描述
在这里插入图片描述
将xml解析成SqlNode之后然后交给DynamicSqlSource,在执行sql的时候就会使用getBoundSql方法,参数为设置的参数值:

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
  	//DynamicContext 为sqlnode执行逻辑后sql的载体
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    //执行对应的元素脚本逻辑即sqlnode逻辑
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    //最后结束就成为了一个staticTextSqlNode了并且将占位符替换好了 即#{}替换成了?
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    //获取boundSql只是new 了一个BoundSql
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    //设置AdditionalParameter
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }

SqlNode的apply方法源码较多,小编挑一个看一下:
ForEachSqlNode:

@Override
  public boolean apply(DynamicContext context) {
  	//获取绑定的参数映射,这边两个,大家着重看下idList为key值为list的
    Map<String, Object> bindings = context.getBindings();
    //通过ognl表达式遍历对应的参数值
    final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    //没值直接返回true
    if (!iterable.iterator().hasNext()) {
      return true;
    }
    boolean first = true;
    //open 就为(
    applyOpen(context);
    int i = 0;
    //遍历
    for (Object o : iterable) {
      DynamicContext oldContext = context;
      //如果是第一个不需要逗号分割否则就用逗号分割
      if (first || separator == null) {
        context = new PrefixedContext(context, "");
      } else {
        context = new PrefixedContext(context, separator);
      }
      int uniqueNumber = context.getUniqueNumber();
      // Issue #709
      //如果foreach为map则进入第一个判断,否则为第二个
      //区别在于index的值,一个是map的key而list是数值
      if (o instanceof Map.Entry) {
        @SuppressWarnings("unchecked")
        Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
        applyItem(context, mapEntry.getValue(), uniqueNumber);
      } else {
        applyIndex(context, i, uniqueNumber);
        applyItem(context, o, uniqueNumber);
      }
      //因为是循环所以每次先放进FilteredDynamicContext中,最后才完全拼接起来
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
      if (first) {
        first = !((PrefixedContext) context).isPrefixApplied();
      }
      context = oldContext;
      i++;
    }
    applyClose(context);
    context.getBindings().remove(item);
    context.getBindings().remove(index);
    return true;
  }

补充值设置过程

上面DynamicSqlSource的getBoundSql方法中有setAdditionalParameter方法,这个方法的作用主要是在设置参数的时候需要用了。小编又又又贴出源码:

@Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          //属性名称
          String propertyName = parameterMapping.getProperty();
          //如果有AdditionalParameter
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
			//则取出的值是这个
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

上面不知道小编解释清楚没有,其实就是多个值的时候如id in (?,?,?)的时候,我们的属性值需要一一对应,且问号的属性名称对应一个值。这样我们的foreach标签就只需要一个值的配置,mybatis给我们做了循环且各自配置了属性名称和值。厉不厉害!

总结

今天小编也彻底讲完了动态sql的解析,其实没有大家想象的那么复杂,从OGNL表达式开始,到SqlSource通过流程解析到可以直接执行的BoundSql结构,然后是sqlsource是怎么来的,以及foreach的参数设置的特殊性通通讲了一遍。希望大家和小编一样,收获满满。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木兮君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值