2024-12-22  阅读(654)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1487827655

回答

支持。 Mybatis 内置了一些动态 SQL 的标签,允许开发者根据不同的条件构建不同的 SQL,提高代码的灵活性和可维护性。如<if><choose><when><otherwise><trim><where><set><foreach>等。

  • <if>元素:根据条件动态包含 SQL 片段。
<mapper namespace="com.damingge.mapper.EmployeeMapper">
    <select id="selectEmployeeByCondition" resultType="com.damingge.model.Employee">
        SELECT * FROM t_employee
        <where>
            <if test="id != null">
                AND id = #{id}
            </if>
            <if test="name != null">
                AND name = #{name}
            </if>
        </where>
    </select>
</mapper>
  • <choose>、<where>、<when>、<otherwise>元素:跟 Java 的 switch 类似。
<mapper namespace="com.damingge.mapper.EmployeeMapper">
    <select id="findUsersByCondition" resultType="com.damingge.model.Employee">
        SELECT * FROM t_employee
        <where>
            <choose>
                <when test="id != null">
                    AND id = #{id}
                </when>
                <when test="name != null">
                    AND name = #{name}
                </when>
                <otherwise>
                    AND status = 'ACTIVE'
                </otherwise>
            </choose>
        </where>
    </select>
</mapper>

  • <trim>、<set>元素:用在动态更新场景。
<mapper namespace="com.damingge.mapper.EmployeeMapper">
    <update id="updateEmployeeById">
        UPDATE t_employee
        <set>
            <if test="name != null">
                name = #{name},
            </if>
            <if test="email != null">
                email = #{email},
            </if>
        </set>
        WHERE id = #{id}
    </update>
</mapper>
  • <foreach>元素:元素遍历场景。
<mapper namespace="com.damingge.mapper.EmployeeMapper">
    <select id="findUsersByIds" resultType="com.damingge.model.Employee">
        SELECT * FROM t_employee
        WHERE id IN
        <foreach item="id" index="index" collection="idList" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>
</mapper>

扩展

动态 SQL 注解

除了 XML 配置动态 SQL 外,mybatis 也提供了对应注解来满足动态 SQL 功能。

  • @Insert
  • @Update
  • @Delete
  • @Select
  • @InsertProvider
  • @UpdateProvider
  • @DeleteProvider
  • @SelectProvider

其中带有 Provider 后缀的区别在于,使用 Provider 需要自身实现方法。

@Update({"<script>",
      "update t_employee",
      "  <set>",
      "    <if test='name != null'>name=#{name},</if>",
      "    <if test='email != null'>email=#{email},</if>",
      "  </set>",
      "where id=#{id}",
      "</script>"})
int updateEmployeeById(Employee employee);

@SelectProvider(type = EmployeeDaoProvider.class, method = "selectEmployeeByName")
Employee selectEmployeeByName(Map<String, Object> map);

public String selectEmployeeByName(Map<String, Object> map) {
        String name = (String) map.get("name");
        String s = new SQL() {
            {
                SELECT("*");
                FROM("Employee");
                if(map.get("name")!=null)            
                WHERE("name=#{name}");
            }
        }.toString();
        return s;
    }
}

SQL 的实现原理

Mybatis 动态 SQL 的实现是通过 XML 标签处理和 OGNL 表达式解析实现的。其中 OGNL 表达式读写 Java Bean 属性值、执行 Java Bean 方法。核心组件:

  • SQL 脚本解析器:负责解析和动态处理 SQL 脚本。
public class XMLScriptBuilder extends BaseBuilder {
  
  private void initNodeHandlerMap() {
    // 初始化动态SQL片段处理器
    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());
  }

  /**
  * 解析 XML 中的 SQL 脚本
  */
  public SqlSource parseScriptNode() {
    // 解析动态 SQL 标签  
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
      // 动态 SQL  
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      // 非动态SQL  
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }
    
  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);
          // 标记为动态SQL
          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);
  }    
}
  • OGNL 表达式解析与替换:对 OGNL 表达式进行求值,将表达式结果替换到 SQL 模板中对应位置。OGNL(Object-Graph Navigation Language) 是一种强大的表达式语言,可以访问对象属性、调用方法以及遍历集合。
public class DynamicSqlSource implements SqlSource {

  private final Configuration configuration;
  private final SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  /**
  * 获取执行SQL(动态SQL解析入口)
  */
  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    // 创建动态SQL上下文,保存解析后的 SQL 语句  
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // 解析 SQL 节点(rootSqlNode 是 Mapper XML 文件中定义的动态SQL节点)
    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;
  }
}
public class ExpressionEvaluator {

  /**
  * 计算满足条件,如IfSqlNode
  */
  public boolean evaluateBoolean(String expression, Object parameterObject) {
    Object value = OgnlCache.getValue(expression, parameterObject);
    if (value instanceof Boolean) {
      return (Boolean) value;
    }
    if (value instanceof Number) {
      return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
    }
    return value != null;
  }

  /**
  * 计算满足条件,ForEachSqlNode
  */  
  public Iterable<?> evaluateIterable(String expression, Object parameterObject) {
    Object value = OgnlCache.getValue(expression, parameterObject);
    if (value == null) {
      throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
    }
    if (value instanceof Iterable) {
      return (Iterable<?>) value;
    }
    if (value.getClass().isArray()) {
      int size = Array.getLength(value);
      List<Object> answer = new ArrayList<>();
      for (int i = 0; i < size; i++) {
        Object o = Array.get(value, i);
        answer.add(o);
      }
      return answer;
    }
    if (value instanceof Map) {
      return ((Map) value).entrySet();
    }
    throw new BuilderException("Error evaluating expression '" + expression + "'.  Return value (" + value + ") was not iterable.");
  }

}
  • 动态 SQL 节点:动态 SQL 节点表示动态 SQL 语句的各个部分。SqlNode是动态 Sql 节点的基类,不同的动态 SQL 标签有不同的实现类。
public interface SqlNode {
    // apply()方法根据传入的实参,解析该 SqlNode 所表示的动态 SQL 内容
    // 将解析之后的 SQL 片段追加到 DynamicContext.sqlBuilder 字段中暂存。
    // 当 SQL 语句中全部的动态 SQL 片段都解析完成后,可以从 DynamicContext.sqlBuilder 字段中得到一条完整的、可用的SQL语句了
    boolean apply(DynamicContext context);
}

Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文