1. 分析
在JFinal中,SQL模板目前对in参数( xxx in (?, ?, ?.... ?) )的解决方案大多如下:
select * from xxx where id in(
#for(x : ids)
#if(!for.first) , #end #para(x)
#end
)
这种方式属于很无奈的解决方案 ,而且当参数个数较少时,还应该能拼写成(id=? OR id=? OR ...... OR id=?)的方式。
那么,有没有更好的解决方案呢?答案是一定的!
2. 解决方案
2.1 in参数
在sql模板中,自定义一个标签来自动生成 ? 占位符,并自动生成与占位符个数相匹配的参数。
SQL模板:WHERE t1.id IN (#inparas(list))
参数:list = [1, 2, 3]
解析结果:
WHERE t1.id IN (?, ?, ?)
#inparas就是处理in参数的自定义标签。
2.1 or参数
SQL模板:WHERE #orparas("t1.id", list)
参数:list = [1, 2, 3]
解析结果:
WHERE t1.id=? OR t1.id=? OR t1.id=?
"t1.id"为字段,#orparas就是处理or参数的自定义标签。
3. 自定义标签
3.1 InParasDirective.java
更正:之前发布的源码中,用于处理OR字串的flag属性是全局共享的,如文档所示:
![]() |
因此,修改源码中的flag属性为局部变量,以保证一个SQL中多次使用此自定义标签时处理首个OR字串不会出错。
感谢JFinal网站的@prelove 和OSChina的@cankongyun
public class InParasDirective extends Directive {
private int index = -1;
public void setExprList(ExprList exprList) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #inparas directive can not be blank", location);
}
if (exprList.length() == 1) {
Expr expr = exprList.getExpr(0);
if (expr instanceof Const && ((Const) expr).isInt()) {
index = ((Const) expr).getInt();
if (index < 0) {
throw new ParseException("The index of inparas array must greater than -1", location);
}
}
}
this.exprList = exprList;
}
@Override
public void exec(Env env, Scope scope, Writer writer) {
SqlPara sqlPara = (SqlPara) scope.get(SqlKit.SQL_PARA_KEY);
if (sqlPara == null) {
throw new TemplateException("#inparas directive invoked by getSqlPara(...) method only", location);
}
Object[] paraArray = this.exprList.evalExprList(scope);
int flag = 0; // IN参数个数
for (int i = 0; i < paraArray.length; i++) {
Object para = paraArray[i];
if (para == null) { // 优先处理参数为Null的情况
flag = this.addPara(sqlPara, para, writer, flag);
} else if (para instanceof Collection<?>) { // 处理参数为Collection的情况
Collection<?> paraCollection = (Collection<?>) para;
for (Object obj : paraCollection) {
flag = this.addPara(sqlPara, obj, writer, flag);
}
} else if (para.getClass().isArray()) { // 处理参数为Array的情况
Object[] objArray = (Object[]) para;
for (Object object : objArray) {
flag = this.addPara(sqlPara, object, writer, flag);
}
} else { // 处理参数为一般类型的情况
flag = this.addPara(sqlPara, para, writer, flag);
}
}
}
private int addPara(SqlPara sqlPara, Object val, Writer writer, int flag) {
write(writer, flag > 0 ? ", ?" : "?");
sqlPara.addPara(val);
return flag + 1;
}3.2 OrParasDirective.java
注意:同上一个自定义标签一样,flag属性改为局部变量。
public class OrParasDirective extends Directive {
public void setExprList(ExprList exprList) {
if (exprList.length() == 0) {
throw new ParseException("The parameter of #orparas directive can not be blank", location);
}
this.exprList = exprList;
}
@Override
public void exec(Env env, Scope scope, Writer writer) {
SqlPara sqlPara = (SqlPara) scope.get(SqlKit.SQL_PARA_KEY);
if (sqlPara == null) {
throw new TemplateException("#orparas directive invoked by getSqlPara(...) method only", location);
}
Object[] paraArray = this.exprList.evalExprList(scope);
int flag = 0; // OR参数个数
String paraName = null;
for (int i = 0; i < paraArray.length; i++) {
Object para = paraArray[i];
if (i == 0) {
if (!(para instanceof String) || StrKit.isBlank((String) para)) {
throw new ParseException("The first parameter must be a string", location);
}
paraName = (String) para;
continue;
}
if (para == null) { // 优先处理参数为Null的情况
flag = this.addPara(sqlPara, para, paraName, writer, flag);
} else if (para instanceof Collection<?>) { // 处理参数为Collection的情况
Collection<?> paraCollection = (Collection<?>) para;
for (Object obj : paraCollection) {
flag = this.addPara(sqlPara, obj, paraName, writer, flag);
}
} else if (para.getClass().isArray()) { // 处理参数为Array的情况
Object[] objArray = (Object[]) para;
for (Object object : objArray) {
flag = this.addPara(sqlPara, object, paraName, writer, flag);
}
} else { // 处理参数为一般类型的情况
flag = this.addPara(sqlPara, para, paraName, writer, flag);
}
}
}
private int addPara(SqlPara sqlPara, Object val, String paraName, Writer writer, int flag) {
write(writer, (flag > 0 ? " OR " : ""));
if (val == null) {
write(writer, paraName + " IS NULL");
} else {
write(writer, paraName + " = ?");
sqlPara.addPara(val);
}
return flag + 1;
}
}4. 测试
自定义的标签在使用前需要先注册进JFinal的SQL模板引擎(注意:不是HTML的模板引擎):
ActiveRecordPlugin arp = new ActiveRecordPlugin( ... );
arp.getEngine().addDirective("inparas", InParasDirective.class);
arp.getEngine().addDirective("orparas", OrParasDirective.class);4.1 SQL模板
标签中的参数可以来自于注入参数、常量、表达式,这些参数均能混合处理,所以,对这些参数做全面的测试就很重要了,请各位仔细观察下面的模板。
4.1.1 in参数模板
#sql("test1")
SELECT * FROM table1 t1 WHERE t1.id IN (#inparas(array))
#end#sql("test2")
SELECT * FROM table1 t2 WHERE t2.id IN (#inparas(list))
#end#sql("test3")
SELECT * FROM table1 t3 WHERE t3.id IN (#inparas(1, 2 + "", 3L, 2 + 2, "5"))
#end#sql("test4")
SELECT * FROM table1 t4 WHERE t4.id IN (#inparas(1, 2 + "", [3L, 2 + 2, "5"]))
#end#sql("test5")
SELECT * FROM table1 t5 WHERE t5.id IN (#inparas(1, 2 + "", [3L, 2 + 2, "5"], array, list, val))
#end
4.2.2 or参数模板
注意,模板中的部分参数特意使用null值:
#sql("test1")
SELECT * FROM table1 t1 WHERE t1.name = "test" AND (#orparas("t1.id", array))
#end#sql("test2")
SELECT * FROM table1 t2 WHERE t2.name = "test" AND (#orparas("t2.id", list))
#end#sql("test3")
SELECT * FROM table1 t3 WHERE t3.name = "test" AND (#orparas("t3.id", 1, 2 + "", 3L, 2 + 2, null))
#end#sql("test4")
SELECT * FROM table1 t4 WHERE t4.name = "test" AND (#orparas("t4.id", null, 2 + "", [3L, 2 + 2, "5"]))
#end#sql("test5")
SELECT * FROM table1 t5 WHERE t5.name = "test" AND (#orparas("t5.id", 1, 2 + "", [null, 2 + 2, "5"], array, list, val))
#end
4.3 注入参数
准备参数:
List<Object> list = new ArrayList<>();
list.add("listVal1");
list.add("listVal2");
list.add("listVal3");
Object[] array = new Object[] { "arrayVal1", "arrayVal2", "arrayVal13" };
// List参数和Array参数再加上个普通参数组合成Kv
Kv.by("array", array).set("list", list).set("val", "valPara");测试代码:
private void testSql(String sqlKey) {
//System.out.println("SQL模板:" + Db.getSql(sqlKey));
SqlPara sqlPara = Db.getSqlPara(sqlKey, Kv.by("array", array).set("list", list).set("val", "valPara"));
System.out.println("解析SQL:" + sqlPara.getSql());
this.printPara(sqlPara.getPara());
}
private void printPara(Object[] paras) {
System.out.print("解析参数:");
if (paras == null) {
System.out.println("null");
return;
}
for (Object p : paras) {
System.out.print(p);
System.out.print(", ");
}
System.out.println();
System.out.println("------------------------------------------------------------------");
System.out.println();
}4.4 测试结果
4.4.1 in参数测试结果
解析SQL:SELECT * FROM table1 t1 WHERE t1.id IN (?, ?, ?)
解析参数:arrayVal1, arrayVal2, arrayVal13
------------------------------------------------------------------解析SQL:SELECT * FROM table1 t2 WHERE t2.id IN (?, ?, ?)
解析参数:listVal1, listVal2, listVal3
------------------------------------------------------------------解析SQL:SELECT * FROM table1 t3 WHERE t3.id IN (?, ?, ?, ?, ?)
解析参数:1, 2, 3, 4, 5
------------------------------------------------------------------解析SQL:SELECT * FROM table1 t4 WHERE t4.id IN (?, ?, ?, ?, ?)
解析参数:1, 2, 3, 4, 5
------------------------------------------------------------------解析SQL:SELECT * FROM table1 t5 WHERE t5.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
解析参数:1, 2, 3, 4, 5, arrayVal1, arrayVal2, arrayVal13, listVal1, listVal2, listVal3, valPara
------------------------------------------------------------------
4.4.2 or参数测试结果
解析SQL:SELECT * FROM table1 t1 WHERE t1.name = "test" AND (t1.id = ? OR t1.id = ? OR t1.id = ?)
解析参数:arrayVal1, arrayVal2, arrayVal13
------------------------------------------------------------------解析SQL:SELECT * FROM table1 t2 WHERE t2.name = "test" AND (t2.id = ? OR t2.id = ? OR t2.id = ?)
解析参数:listVal1, listVal2, listVal3
------------------------------------------------------------------解析SQL:SELECT * FROM table1 t3 WHERE t3.name = "test" AND (t3.id = ? OR t3.id = ? OR t3.id = ? OR t3.id = ? OR t3.id IS NULL)
解析参数:1, 2, 3, 4
------------------------------------------------------------------解析SQL:SELECT * FROM table1 t4 WHERE t4.name = "test" AND (t4.id IS NULL OR t4.id = ? OR t4.id = ? OR t4.id = ? OR t4.id = ?)
解析参数:2, 3, 4, 5
------------------------------------------------------------------解析SQL:SELECT * FROM table1 t5 WHERE t5.name = "test" AND (t5.id = ? OR t5.id = ? OR t5.id IS NULL OR t5.id = ? OR t5.id = ? OR t5.id = ? OR t5.id = ? OR t5.id = ? OR t5.id = ? OR t5.id = ? OR t5.id = ? OR t5.id = ?)
解析参数:1, 2, 4, 5, arrayVal1, arrayVal2, arrayVal13, listVal1, listVal2, listVal3, valPara
------------------------------------------------------------------
可以看到,参数为空时被替换成IS NULL语法,完美!
代码只经过了单元测试,没有实际项目使用过,各位在使用时发现问题请一定要告知,谢谢。。。
