MyBatis自定义自定义动态SQL解析方式

引子

之前项目中一直使用的是JPA作为ORM框架,最近,新加了一个子服务,使用的是 MyBatis 作为ORM框架。既然用的是MyBatis,那就免不了 循环迭代参数、if 动态SQL查询等。

然而,MyBatis 的动态SQL要写很多判断逻辑、迭代逻辑,老是从其它SQL中去Copy就显得太Low了。所以,我查了查MyBatis的官方文档,最终找到了解决方案,这里我将方法分享出来,希望能帮到大家。

解决方案

翻阅了官方文档,我看到了下面这段话:

MyBatis 从 3.2 开始支持可插拔脚本语言,这允许你插入一种脚本语言驱动,并基于这种语言来编写动态 SQL 查询语句。

同时,我还得知:我们可以通过自己实现 LanguageDriver 接口,来自定义我们的SQL脚本解析协议。而在默认情况下,我们使用的XML格式的动态SQL,都是通过 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver 去解析的。

所以,我们可以通过重写 XmlLanguageDriver 的相关方法,来实现自定义的动态SQL。

核心代码

下面我给出了SQL解析与使用的核心代码,完整代码请参考 代码附件

下面,我自定义了 IN 查询的 循环语法、if 查询的语法:

public class CustomXmlLanguageDriver extends XMLLanguageDriver {
    /**
     * 自定义 IN 查询语法规则 正则 (#{xxCollection})
     */
    private static final Pattern CUSTOM_IN_QUERY_RULE = Pattern.compile("IN\\s*\\(\\s*#\\{(.*?)}\\s*\\)", Pattern.CASE_INSENSITIVE);
    /**
     * 自定义 if 条件查询 if(name != null and someTable.column != "" ) [a.name = #{name} and other expressions]
     */
    private static final Pattern CUSTOM_IF_NULL_RULE = Pattern.compile("if\\s*\\((.*?)\\)\\s*\\[(.*?)]", Pattern.CASE_INSENSITIVE);

    private static final Pattern SCRIPT_TAG_PATTERN = Pattern.compile("^<script>.*</script>$");

    @Override
    public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        boolean scriptFlag = false;
        Matcher inRuleMatcher = CUSTOM_IN_QUERY_RULE.matcher(script);
        if (inRuleMatcher.find()) {
            script = inRuleMatcher.replaceAll(" IN (<foreach collection='$1' item='element' separator=','> #{element} </foreach>) ");
            scriptFlag = true;
        }
        Matcher ifRuleMatcher = CUSTOM_IF_NULL_RULE.matcher(script);
        if (ifRuleMatcher.find()) {
            script = ifRuleMatcher.replaceAll(" <if test = \"$1\"> $2 </if> ");
            scriptFlag = true;
        }
        if (scriptFlag) {
            // 检测SQL是否被<script>包裹
            Matcher scriptTagPattern = SCRIPT_TAG_PATTERN.matcher(script);
            if (!scriptTagPattern.find()) {
                script = "<script>" + script + "</script>";
            }
        }
        return super.createSqlSource(configuration, script, parameterType);
    }
}

上面,我重写了 createSqlSource() 方法,在里面加入了自定义的SQL脚本解析逻辑。我是通过正则去匹配的,当然,大家可以自己实现字符串的匹配方式。这里,需要额外说明的几点是:

  • Pattern.CASE_INSENSITIVE 代表的是,匹配的时候,忽略大小写。if 可以匹配到 if 和 IF。
  • Mather.replaceAll()中的 $1 $2,分别等价于  matcher.group(1)、matcher.group(2) 的值。

上面我们自定义了 foreach 和 if 的动态SQL后,下面就是具体的使用了:

@Mapper
public interface UserMapper {
    @Lang(CustomXmlLanguageDriver.class)
    @Select({"  SELECT u.id, u.account, u.role ",
            "     FROM user u",
            "    WHERE u.id IN (#{ids}) ",
            "      if (role != null and role != '') [AND u.role = #{role}] "})
    List<User> getUserByIdAndRole(@Param("ids") Collection<Integer> ids, @Param("role") String role);
}

上面,我们通过 @Lang 注解标注该方法,表示使用我们重写的 CustomXmlLanguageDriver 类来解析下面的SQL脚本。按照我们正则定义逻辑,当我们使用 (#{ids}) 的时候,这一部分就会被替换为:

 (<foreach collection='#{ids}' item='element' separator=','>
     #{element}
 </foreach>) 

而我们使用 if (role != null and role != '') [AND u.role = #{role}] 的时候,这一部分就会被替换为:

 <if test = "role != null and role != '' "> 
    AND u.role = #{role}
 </if> 

这样对比下来,我们自定义的语法的确是方便了很多。

代码附件

GitHub:https://github.com/Zereao/SpringBucket/tree/master/spring-boot-mybatis-custom-language-driver

完整代码:https://download.csdn.net/download/Zereao/12031773

参考文档

1、https://mybatis.org/mybatis-3/zh/dynamic-sql.html

2、http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值