让SQL模板指令#para()支持默认值

在使用SQL模板参数时,很容易遇到开始日期和结束日期的查询,通常这种查询使用Between的效率最高。而JFinal自带的#para()指令就很尴尬了,它不支持参数为空时使用默认值。因此在模板中只能写成:

BETWEEN if(START_DATE) #para(START_DATE) #else #para('1000-01-01 00:00:00') #end
    AND if(END_DATE) #para(END_DATE) #else #para('9999-12-31 23:59:59') #end

可以看到,写法相当之别扭。与波总沟通后,决定自己扩展个指令#parad()来支持默认值,于是上面的SQL就可以写成:

BETWEEN #parad(START_DATE, '1000-01-01 00:00:00')
    AND #parad(  END_DATE, '9999-12-31 23:59:59')

这样就顺眼多了。。。

可是要达到这样的程度,就需要2步走:

  1.  实现扩展指令#parad()

  2. 注册扩展指令到ActiveRecordPlugin的SQL模板引擎


第1步,扩展指令#parad():

public class ParadDirective extends Directive {

	private int index = -1;
	private String paraName = null;
	private static boolean checkParaAssigned = true;

	public static void setCheckParaAssigned(boolean checkParaAssigned) {
		ParadDirective.checkParaAssigned = checkParaAssigned;
	}

	public void setExprList(ExprList exprList) {
		if (exprList.length() == 0) {
			throw new ParseException("The parameter of #para 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 para array must greater than -1", location);
				}
			}
		}

		if (checkParaAssigned && exprList.getLastExpr() instanceof Id) {
			Id id = (Id)exprList.getLastExpr();
			paraName = id.getId();
		}

		this.exprList = exprList;
	}

	public void exec(Env env, Scope scope, Writer writer) {
		SqlPara sqlPara = (SqlPara)scope.get(SqlKit.SQL_PARA_KEY);
		if (sqlPara == null) {
			throw new TemplateException("#para directive invoked by getSqlPara(...) method only", location);
		}

		write(writer, "?");
		if (index == -1) {
			// #para(paraName) 中的 paraName 没有赋值时抛出异常
			// issue: http://www.jfinal.com/feedback/1832
			if (checkParaAssigned && paraName != null && !scope.exists(paraName)) {
				throw new TemplateException("The parameter \""+ paraName +"\" must be assigned", location);
			}

			// 屏蔽掉原代码
			//sqlPara.addPara(exprList.eval(scope));
			Object[] array =  exprList.evalExprList(scope);
			if(array[0] != null && (array[0] instanceof String && StrKit.notBlank((String) array[0]))) { //校验的同时,String类型参数不能为空字串
				// 第一个参数不为空则注册参数到sqlPara
				sqlPara.addPara(array[0]);
			} else if(array.length > 1) {
				// 将Default值注册到sqlPara
				sqlPara.addPara(array[array.length -1 ]);
			} else {
				throw new TemplateException("The parameter \""+ paraName +"\" must be assigned or give a default value", location);
			}
		} else {
			Object[] paras = (Object[])scope.get(SqlKit.PARA_ARRAY_KEY);
			if (paras == null) {
				throw new TemplateException("The #para(" + index + ") directive must invoked by getSqlPara(String, Object...) method", location);
			}
			if (index >= paras.length) {
				throw new TemplateException("The index of #para directive is out of bounds: " + index, location);
			}
			sqlPara.addPara(paras[index]);
		}
	}

将原#para()指令中的代码原样抄一遍,然后上面代码中“屏蔽掉原代码”之后的才是重点,这里我就不解释源码了,看客自己分析吧。

第2步,注册扩展指令到ActiveRecordPlugin的SQL模板引擎

// 数据操作插件
ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);

// 注册支持默认参数值的标签
arp.getEngine().addDirective("parad", ParadDirective.class);

同样,自己看,不解释。


到此,支持默认参数的#parad()指令就可以使用了。@JFinal

注意,#para()和#parad()就差了个字母d,各位看仔细了哦。

评论区

山东小木

2019-05-08 17:20

实用 赞

杜福忠

2019-05-08 17:45

赞!不知道“空合并安全取值调用操作符”?? 可以用不, 回头实验一下
#para(val ?? '0000-01-01 00:00:00')

JFinal

2019-05-08 17:48

建议指令名称由 parad 改为 paradef,以免看错,这个分享挺有用

JFinal

2019-05-08 17:49

@杜福忠 空合并表达式应该也可以的,赞

糊搞

2019-05-08 17:54

@杜福忠 已经验证#para(START_DATE ?? '1000-01-01 00:00:00')这种写法,如果参数为null是可以的,但参数为“”这种空字串时还是无法生效

杜福忠

2019-05-08 18:06

@糊搞 嗯,对,这是个梗。哎对了,根据你这个原理, 还可以使用模版函数也可以达到这样的效果。在公共的模版地方增加一个 :
#define paradef(val, defVal)
#if(val) #para(val) #else #para(defVal) #end
#end
使用的时候 #@paradef(START_DATE, '1000-01-01 00:00:00')
这个肯定可以

JFinal

2019-05-08 18:08