MyBatis 入门 (七)

MyBatis 动态 SQL

为方便理解,本章涉及示例代码已上传至 gitee
==>获取示例代码请点击这里。。。
拉取示例代码时,请拉取所有分支,master 分支只是做了示例的初始化

MyBatis 的是通过在 XML 中支持一下几种标签来实现动态 SQL 的:

  • if
  • choose(when、otherwise)
  • trim(where、set)
  • foreach
  • bind
    下面依次介绍:

if 用法

示例如下:

<select id="selectAllUseIf" resultMap="UserEntity" >
    select * from sys_user
        where
            1 = 1
            <if test="id != null and id != ''">
            and id = #{id}
            </if>
</select>
List<SysUser> selectAllUseIf(@Param("id") Integer id);

接口方法中正常使用即可, if 标签中有一个 test 的属性,该属性的值应该是一个判断条件,返回 true 则 if 标签内的内容会被拼接到 SQL 中,返回 false 则跳过。判断条件中的 properties != null 或者 properties == null 适用于任何类型的字段是否为空的判断,properties != ‘’ 或者 properties == ‘’ 仅适用于 String 类型的字段判断,判断字段是否为空字符串。and 或者 or 则相当于 Java if 判断中的 && 和 || ,当有嵌套的判断时,和 Java 中的 if 判断一样使用小括号分组。

需要注意的一点是: if 标签内的 SQL 语句是以 and 开头的,并且 where 后跟一个恒等的 1=1 的条件,当判断条件为 true 时,SQL 打印如下:

select * from sys_user where 1 = 1 and id = ? 

判断条件为 false 时,如果不跟 1=1 的恒等条件的话,拼接起来的 SQL 像下面这样:

select * from sys_user where and id = ? 

该条 SQL 一旦被执行则会报错。请在编写 SQL 的时候留意该处,确保代码的健壮性。

下面来看一下 MyBatis 具体解析 标签的流程:

  1. 初始化 —> 绑定动态 SQL 语句
    由于我们当前 MyBatis 配置文件中是通过 mapper resource = “…” 标签来加载 XML Mapper 文件的,这里就只贴出使用 mapper 标签时的流程图:在这里插入图片描述
    关于 package name = “” 标签加载 XML Mapper 文件的流程图请参考 Use MyBatis By Annotation 这一节中的 @Select 下的流程图,虽然这两种配置方式的代码调用流程不同,但是最终都是通过调用到
XMLLanguageDriver # createSqlSource(Configuration configuration, XNode script, Class<?> parameterType)

完成 SQL 语句的绑定。

  1. 执行时 -->组装 SQL
    MyBatis 组装 SQL 的方法如下:
DynamicSqlSource.class

@Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    // 在这一步完成 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);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

choose 用法

示例如下:

<select id="selectByIdUseChoose" resultMap="UserEntity" >
    select * from sys_user 
    where 
    <choose>
        <when test="id != null and id != ''">
        	id = #{id}
        </when>
        <otherwise>
        	1 = 2
        </otherwise>
    </choose>
</select>
public interface SysUserMapper {
	// other Methods ... 
    
    SysUser selectByIdUseChoose(@Param("id") Integer id);
}

标签主要用来实现类似与下面的判断:

if(condition){
	// do something...
}else {
	// do something ...
}

其中 choose 标签下的 when test = “” 子标签对应 if(condition) ,otherwise 子标签对应于 else 部分。

choose 标签的使用注意点:使用过程中注意拼接出来的 SQL 语句的规范性。

trim 用法

关于 trim 标签的话,因为 MyBatis 对动态 SQL 的解析是通过将 XML 节点解析为 Java 对象,所以,这里我们可以直接去看一下 TrimSqlNode 这个类,XML 中 trim 节点的属性就是 TrimSqlNode 这个类的成员属性:

TrimSqlNode.class

public class TrimSqlNode implements SqlNode {
  // 当前节点内容
  private final SqlNode contents;
  // 前缀; XML 中 <trim> 节点的属性值之一
  private final String prefix;
  // 后缀; XML 中 <trim> 节点的属性值之一
  private final String suffix;
  // 生成的该条 SQL 中,前缀需要被覆盖掉的值; XML 中 <trim> 节点的属性值之一
  private final List<String> prefixesToOverride;
  // 生成的该条 SQL 中,后缀需要被覆盖掉的值; XML 中 <trim> 节点的属性值之一
  private final List<String> suffixesToOverride;
  // MyBatis 的配置信息
  private final Configuration configuration;

  // other Methods...

}

下面为示例代码:

<select id="selectByIdUseTrim" resultMap="UserEntity" >
    select * from sys_user
    <trim prefix="where" prefixOverrides="and" >
        <if test="id != null and id != ''">
        and id = #{id}
        </if>
    </trim>
</select>
public interface SysUserMapper {
    // other Methods ...

    SysUser selectByIdUseTrim(@Param("id") Integer id);
}

这里具体看一下 trim 是如何做前后缀处理的拼接的:

具体的方法是 TrimSqlNode 类中的:

public void applyAll() {
	// SQL 字符串做 trim 操作
    sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
    // SQL 转为 大写
    String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
    if (trimmedUppercaseSql.length() > 0) {
    	// 对前缀进行操作:
    	// 第一步:去除 SQL 字符串中包含 prefixesToOverride 值的字符
    	// 第二步:在 SQL 字符串前添加 prefix 的值和一个空字符
        applyPrefix(sqlBuffer, trimmedUppercaseSql);
        // 对后缀进行操作:
    	// 第一步:去除 SQL 字符串中包含 suffixesToOverride 值的字符
    	// 第二步:在 SQL 字符串前添加 suffix 的值和一个空字符
        applySuffix(sqlBuffer, trimmedUppercaseSql);
    }
    // 将 <trim> 标签中的字符与其他 SQL 拼接起来
    delegate.appendSql(sqlBuffer.toString());
}

where 用法

where 标签对应 Java 类是以 TrimSqlNode 为父类实现的,where 标签是没有属性的,因为在 MyBatis 处理对应的 where 标签的时候,执行的是其父类的操作方法,where 标签对应 Java 类中只是定义一个 prefixList,这个参数会被传递到 TrimSqlNode 类中的 prefixesToOverride 属性上,同时传入父类的还有一个 “WHERE” 字符串,传给父类的 prefix 属性,所以,在解析 where 标签时,会删除 SQL 字符串中 prefixesToOverride 内包含的字符,并在字符前拼接一个 “WHERE“ 字符和一个空字符,其他不做处理。

下面为 WhereSqlNode 类的具体代码:

public class WhereSqlNode extends TrimSqlNode {

  private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");

  public WhereSqlNode(Configuration configuration, SqlNode contents) {
    super(configuration, contents, "WHERE", prefixList, null, null);
  }

}

set 用法

set 标签对应 Java 类也是以 TrimSqlNode 为父类实现的,t同样没有属性,同样执行的是其父类的操作方法,set 标签对应 Java 类中只是定义了一个 COMMA,这个参数会被同时传递到 TrimSqlNode 类中的 prefixesToOverride 属性和 suffixesToOverride 属性上,同时传入父类的还有一个 “SET” 字符串,传给父类的 prefix 属性,所以,在解析 set 标签时,会删除 SQL 字符串中 COMMA内包含的字符,并在字符前拼接一个 “SET“ 字符和一个空字符,其他不做处理。

下面为 SetSqlNode 类的具体代码:

public class SetSqlNode extends TrimSqlNode {

  private static final List<String> COMMA = Collections.singletonList(",");

  public SetSqlNode(Configuration configuration,SqlNode contents) {
    super(configuration, contents, "SET", COMMA, null, COMMA);
  }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值