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。

  1. package com.jfinal.plugin.activerecord.sql;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.Collection;
  5. import java.util.List;
  6.  
  7. import com.jfinal.plugin.activerecord.SqlPara;
  8. import com.jfinal.template.Env;
  9. import com.jfinal.template.TemplateException;
  10. import com.jfinal.template.expr.ast.ExprList;
  11. import com.jfinal.template.io.Writer;
  12. import com.jfinal.template.stat.ParseException;
  13. import com.jfinal.template.stat.Scope;
  14.  
  15. import io.jboot.web.directive.base.JbootDirectiveBase;
  16.  
  17. public class LikeParaDirective extends JbootDirectiveBase {
  18.  
  19.     public static final String DIRECTIVE_NAME = "likePara";
  20.  
  21.     @Override
  22.     public void onRender(Env env, Scope scope, Writer writer) {
  23.         SqlPara sqlPara = (SqlPara)scope.get(SqlKit.SQL_PARA_KEY);
  24.  
  25.         if (sqlPara == null) {
  26.             throw new TemplateException("#" + DIRECTIVE_NAME + " directive invoked by getSqlPara(...) method only", this.location);
  27.         }
  28.  
  29.         Object[] paraArray = this.exprList.evalExprList(scope);
  30.  
  31.         List<Object> paraList = new ArrayList<>();
  32.  
  33.         /*
  34.          * 首先将参数全规划成List
  35.          */
  36.         for (Object para : paraArray) {
  37.             if (para == null) { // 优先处理参数为Null的情况
  38.                 throw new ParseException("The parameter of #" + DIRECTIVE_NAME + " directive can not be null",
  39.                     this.location);
  40.             } else if (para instanceof Collection<?>) { // 处理参数为Collection的情况
  41.                 paraList.addAll((Collection<?>)para);
  42.             } else if (para.getClass().isArray()) { // 处理参数为Array的情况
  43.                 for (Object object : (Object[])para) {
  44.                     paraList.add(object);
  45.                 }
  46.             } else { // 处理参数为一般类型的情况
  47.                 paraList.add(para);
  48.             }
  49.         }
  50.  
  51.         boolean leftLike = false;
  52.         boolean rightLike = false;
  53.  
  54.         /*
  55.          * 对规划好的参数List进行处理
  56.          */
  57.         // 处理左百分号(%)
  58.         if ("%".equals(paraList.get(0))) {
  59.             // 单独为百分号的情况
  60.             leftLike = true;
  61.             paraList.remove(0);
  62.         } else if (String.valueOf(paraList.get(0)).startsWith("%")) {
  63.             // 百分号开头的情况
  64.             leftLike = true;
  65.             paraList.set(0, String.valueOf(paraList.get(0)).substring(1));
  66.         }
  67.  
  68.         // 处理右百分号(%)
  69.         if (paraList.size() > 0) {
  70.             int lastIndex = paraList.size() - 1;
  71.             if ("%".equals(paraList.get(lastIndex))) {
  72.                 // 单独为百分号的情况
  73.                 rightLike = true;
  74.                 paraList.remove(lastIndex);
  75.             } else if (String.valueOf(paraList.get(lastIndex)).endsWith("%")) {
  76.                 // 百分号结尾的情况
  77.                 rightLike = true;
  78.                 paraList.set(lastIndex,
  79.                     String.valueOf(paraList.get(0)).substring(0, String.valueOf(paraList.get(0)).length() - 1));
  80.             }
  81.         }
  82.  
  83.         /*
  84.          * 构建LIKE参数字串
  85.          */
  86.         StringBuilder sb = new StringBuilder();
  87.         // 左百分号(%)
  88.         sb.append(leftLike ? '%' : "");
  89.         sb.append(!(leftLike || rightLike) ? '%' : ""); // 左右都不包括百分号,则默认加上
  90.         // 参数字串
  91.         for (Object para : paraList) {
  92.             sb.append(para);
  93.         }
  94.         // 右百分号(%)
  95.         sb.append(rightLike ? '%' : "");
  96.         sb.append(!(leftLike || rightLike) ? '%' : ""); // 左右都不包括百分号,则默认加上
  97.  
  98.         /*
  99.          * 将解析好的参数给设置到SQL模板中
  100.          */
  101.         this.write(writer, "LIKE ?");
  102.  
  103.         sqlPara.addPara(sb.toString());
  104.     }
  105.  
  106.     @Override
  107.     public void setExprList(ExprList exprList) {
  108.  
  109.         if (exprList.length() == 0) {
  110.             throw new ParseException("The parameter of #" + DIRECTIVE_NAME + " directive can not be blank",
  111.                 this.location);
  112.         }
  113.  
  114.         this.exprList = exprList;
  115.     }
  116.  
  117. }


    4. 测试

        结果:

  1. ---------------------------------------------------
  2. 模板:#likePara('%', val, [1, '2', 3L], "4", '%')
  3. 入参:{"val":["v","alue",0]}
  4. 结果:LIKE '%value01234%'
  5.  
  6. ---------------------------------------------------
  7. 模板:#likePara('%', val, [1, '2', 3L], "4", '%')
  8. 入参:{"val":["valu","e",0]}
  9. 结果:LIKE '%value01234%'
  10.  
  11. ---------------------------------------------------
  12. 模板:#likePara(val, '%')
  13. 入参:{"val":"value"}
  14. 结果:LIKE 'value%'
  15.  
  16. ---------------------------------------------------
  17. 模板:#likePara(val, '%')
  18. 入参:{"val":"%value"}
  19. 结果:LIKE '%value%'
  20.  
  21. ---------------------------------------------------
  22. 模板:#likePara(val, '%')
  23. 入参:{"val":"va%lue"}
  24. 结果:LIKE 'va%lue%'
  25.  
  26. ---------------------------------------------------
  27. 模板:#likePara('%', val, '%')
  28. 入参:{"val":"value"}
  29. 结果:LIKE '%value%'
  30.  
  31. ---------------------------------------------------
  32. 模板:#likePara('%', val, '%')
  33. 入参:{"val":"value%"}
  34. 结果:LIKE '%value%%'
  35.  
  36. ---------------------------------------------------
  37. 模板:#likePara('%', val, '%')
  38. 入参:{"val":"va%lue"}
  39. 结果:LIKE '%va%lue%'
  40.  
  41. ---------------------------------------------------
  42. 模板:#likePara('%')
  43. 入参:{}
  44. 结果:LIKE '%'
  45.  
  46. ---------------------------------------------------
  47. 模板:#likePara(val)
  48. 入参:{"val":"%"}
  49. 结果:LIKE '%'
  50.  
  51. ---------------------------------------------------
  52. 模板:#likePara('%', val)
  53. 入参:{"val":"value"}
  54. 结果:LIKE '%value'
  55.  
  56. ---------------------------------------------------
  57. 模板:#likePara('%', val)
  58. 入参:{"val":"value%"}
  59. 结果:LIKE '%value%'
  60.  
  61. ---------------------------------------------------
  62. 模板:#likePara('%', val)
  63. 入参:{"val":"va%lue"}
  64. 结果:LIKE '%va%lue'
  65.  
  66. ---------------------------------------------------
  67. 模板:#likePara(val)
  68. 入参:{"val":"value"}
  69. 结果:LIKE '%value%'
  70.  
  71. ---------------------------------------------------
  72. 模板:#likePara(val)
  73. 入参:{"val":"%value"}
  74. 结果:LIKE '%value'
  75.  
  76. ---------------------------------------------------
  77. 模板:#likePara(val)
  78. 入参:{"val":"value%"}
  79. 结果:LIKE 'value%'
  80.  
  81. ---------------------------------------------------
  82. 模板:#likePara(val)
  83. 入参:{"val":"va%lue"}
  84. 结果:LIKE '%va%lue%'
  85.  
  86. ---------------------------------------------------
  87. 模板:#likePara(val)
  88. 入参:{"val":["v","alue"]}
  89. 结果:LIKE '%value%'
  90.  
  91. ---------------------------------------------------
  92. 模板:#likePara(val)
  93. 入参:{"val":["val","ue"]}
  94. 结果: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

热门分享

扫码入社