SQL模板中一个比较理想的自定义like指令

    1. 分析

    1. JFinal中刚新增like指令

      在这之前大家的通常用法一般是自己拼装,如MySQL的用法:like concat('%', #para(title), '%')

      自带的like指令功能单一,个人认为需要增强。

    2. 大神的like指令

      https://jfinal.com/share/398

    2. 方案

        为了和JFinal新增指令不冲突,本指令命名为likePara。

        like指令应该具备通用性强、自动适配百分号、支持多参数拼装等:

  • WHERE t.title #likePara("title", [1, '2', 3L], "4"),被解析为WHERE t.title LIKE ?,参数值为:'%title1234%';可以看出,多个不同类型的参数会toString()拼装,两端没有百分号则自动补充百分号,拼装规则为全Like;

  • #likePara('%', "value") 和 #likePara("%value"),参数值为:'%value',左边出现了百分号,拼装规则为左Like;同样,右Like拼装方式也一样;


    3. 自定义标签

        源码,基于Jboot的JbootDirectiveBase。

package com.jfinal.plugin.activerecord.sql;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.jfinal.plugin.activerecord.SqlPara;
import com.jfinal.template.Env;
import com.jfinal.template.TemplateException;
import com.jfinal.template.expr.ast.ExprList;
import com.jfinal.template.io.Writer;
import com.jfinal.template.stat.ParseException;
import com.jfinal.template.stat.Scope;

import io.jboot.web.directive.base.JbootDirectiveBase;

public class LikeParaDirective extends JbootDirectiveBase {

    public static final String DIRECTIVE_NAME = "likePara";

    @Override
    public void onRender(Env env, Scope scope, Writer writer) {
        SqlPara sqlPara = (SqlPara)scope.get(SqlKit.SQL_PARA_KEY);

        if (sqlPara == null) {
            throw new TemplateException("#" + DIRECTIVE_NAME + " directive invoked by getSqlPara(...) method only", this.location);
        }

        Object[] paraArray = this.exprList.evalExprList(scope);

        List<Object> paraList = new ArrayList<>();

        /*
         * 首先将参数全规划成List
         */
        for (Object para : paraArray) {
            if (para == null) { // 优先处理参数为Null的情况
                throw new ParseException("The parameter of #" + DIRECTIVE_NAME + " directive can not be null",
                    this.location);
            } else if (para instanceof Collection<?>) { // 处理参数为Collection的情况
                paraList.addAll((Collection<?>)para);
            } else if (para.getClass().isArray()) { // 处理参数为Array的情况
                for (Object object : (Object[])para) {
                    paraList.add(object);
                }
            } else { // 处理参数为一般类型的情况
                paraList.add(para);
            }
        }

        boolean leftLike = false;
        boolean rightLike = false;

        /*
         * 对规划好的参数List进行处理
         */
        // 处理左百分号(%)
        if ("%".equals(paraList.get(0))) {
            // 单独为百分号的情况
            leftLike = true;
            paraList.remove(0);
        } else if (String.valueOf(paraList.get(0)).startsWith("%")) {
            // 百分号开头的情况
            leftLike = true;
            paraList.set(0, String.valueOf(paraList.get(0)).substring(1));
        }

        // 处理右百分号(%)
        if (paraList.size() > 0) {
            int lastIndex = paraList.size() - 1;
            if ("%".equals(paraList.get(lastIndex))) {
                // 单独为百分号的情况
                rightLike = true;
                paraList.remove(lastIndex);
            } else if (String.valueOf(paraList.get(lastIndex)).endsWith("%")) {
                // 百分号结尾的情况
                rightLike = true;
                paraList.set(lastIndex,
                    String.valueOf(paraList.get(0)).substring(0, String.valueOf(paraList.get(0)).length() - 1));
            }
        }

        /*
         * 构建LIKE参数字串
         */
        StringBuilder sb = new StringBuilder();
        // 左百分号(%)
        sb.append(leftLike ? '%' : "");
        sb.append(!(leftLike || rightLike) ? '%' : ""); // 左右都不包括百分号,则默认加上
        // 参数字串
        for (Object para : paraList) {
            sb.append(para);
        }
        // 右百分号(%)
        sb.append(rightLike ? '%' : "");
        sb.append(!(leftLike || rightLike) ? '%' : ""); // 左右都不包括百分号,则默认加上

        /*
         * 将解析好的参数给设置到SQL模板中
         */
        this.write(writer, "LIKE ?");

        sqlPara.addPara(sb.toString());
    }

    @Override
    public void setExprList(ExprList exprList) {

        if (exprList.length() == 0) {
            throw new ParseException("The parameter of #" + DIRECTIVE_NAME + " directive can not be blank",
                this.location);
        }

        this.exprList = exprList;
    }

}


    4. 测试

        结果:

---------------------------------------------------
模板:#likePara('%', val, [1, '2', 3L], "4", '%')
入参:{"val":["v","alue",0]}
结果:LIKE '%value01234%'

---------------------------------------------------
模板:#likePara('%', val, [1, '2', 3L], "4", '%')
入参:{"val":["valu","e",0]}
结果:LIKE '%value01234%'

---------------------------------------------------
模板:#likePara(val, '%')
入参:{"val":"value"}
结果:LIKE 'value%'

---------------------------------------------------
模板:#likePara(val, '%')
入参:{"val":"%value"}
结果:LIKE '%value%'

---------------------------------------------------
模板:#likePara(val, '%')
入参:{"val":"va%lue"}
结果:LIKE 'va%lue%'

---------------------------------------------------
模板:#likePara('%', val, '%')
入参:{"val":"value"}
结果:LIKE '%value%'

---------------------------------------------------
模板:#likePara('%', val, '%')
入参:{"val":"value%"}
结果:LIKE '%value%%'

---------------------------------------------------
模板:#likePara('%', val, '%')
入参:{"val":"va%lue"}
结果:LIKE '%va%lue%'

---------------------------------------------------
模板:#likePara('%')
入参:{}
结果:LIKE '%'

---------------------------------------------------
模板:#likePara(val)
入参:{"val":"%"}
结果:LIKE '%'

---------------------------------------------------
模板:#likePara('%', val)
入参:{"val":"value"}
结果:LIKE '%value'

---------------------------------------------------
模板:#likePara('%', val)
入参:{"val":"value%"}
结果:LIKE '%value%'

---------------------------------------------------
模板:#likePara('%', val)
入参:{"val":"va%lue"}
结果:LIKE '%va%lue'

---------------------------------------------------
模板:#likePara(val)
入参:{"val":"value"}
结果:LIKE '%value%'

---------------------------------------------------
模板:#likePara(val)
入参:{"val":"%value"}
结果:LIKE '%value'

---------------------------------------------------
模板:#likePara(val)
入参:{"val":"value%"}
结果:LIKE 'value%'

---------------------------------------------------
模板:#likePara(val)
入参:{"val":"va%lue"}
结果:LIKE '%va%lue%'

---------------------------------------------------
模板:#likePara(val)
入参:{"val":["v","alue"]}
结果:LIKE '%value%'

---------------------------------------------------
模板:#likePara(val)
入参:{"val":["val","ue"]}
结果:LIKE '%value%'


评论区

山东小木

2022-04-14 15:15

JFinal里已经有内置like了

happyboy

2022-04-14 16:45

sql注入大军正在赶来的路上

糊搞

2022-04-14 17:16

@happyboy 看清楚,用的是占位符参数,不会有注入

happyboy

2022-04-14 17:24

@糊搞 (⊙o⊙)…,刚才看波总最新代码,已经加入like指令了。

糊搞

2022-04-14 17:37

@happyboy 等会改一下指令名,不与波总冲突

糊搞

2022-04-14 17:49

happyboy

2022-04-14 18:48

@糊搞 (((o(*゚▽゚*)o)))

chcode

2022-04-21 18:28

如无必要,勿增实体,还是没有这个指令的好

糊搞

2022-04-21 22:41

@JFinal good, good, good

JFinal

2022-04-21 22:43

jfinal 4.9.23 的 #para 指令新增了对 like 的支持,map 传参用法如下:
select * from t where title like #para(title, "like")
Java 代码如下:
Db.template(sqlKey, Kv.of("title", "article title here").find();

int 传参用法如下:
select * from t where title like #para(0, "like")
Java 代码如下:
Db.template(sqlKey, "title 参数值").find();

#para 指令还支持另外两种模式:
#para(xxx, "%like")
#para(xxx, "%like%")

此外还支持 in 参数:
select * from t where id in #para(idList, "in")
java 代码如下:
List idList = service.getIdList();
Db.template(sqlKey, Kv.of("idList", idList).find();

最终一个 #para 实现了三种传参方式:传统 + like + in

热门分享

扫码入社