5.13 SQL 管理与动态生成
JFinal利用自带的Template Engine极为简洁的实现了Sql管理功能。一如既往的极简设计,仅有#sql、#para、#namespace三个指令,学习成本依然低到极致。
重要:除了以上三个 sql 管理专用指令以外,jfinal 模板引擎的所有指令和功能也可以用在 sql 管理,jfinal 模板引擎用法见第 6 章:http://www.jfinal.com/doc/6-1
1、基本配置
在ActiveRecordPlugin中使用sql管理功能示例代码如下:
- ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
- arp.getEngine().setSourceFactory(new ClassPathSourceFactory());
- arp.addSqlTemplate("all.sql");
- _MappingKit.mapping(arp);
- me.add(arp);
如上例所示,arp.getEngine().setSourceFactory(new ClassPathSourceFactory()) 这行代码,设置了模板引擎将从 class path 或者 jar 包中读取 sql 文件。 可以将 sql 文件放在maven项目下的resources 之下,编译器会自动将其编译至 class path 之下,进而可以被读取到。
第三行代码通过 arp.addSqlTemplate(…) 添加外部 sql 模板文件,可以通过多次调用addSqlTemplate来添加任意多个外部 sql 文件,并且对于不同的 ActiveRecordPlugin 对象都是彼此独立配置的,有利于多数据源下对 sql 进行模块化管理。
如果希望在开发阶段可以对修改的sql文件实现热加载,可以配置arp.setDevMode(true)或者arp.getEngine().setDevMode(true),如果不配置则默认使用configConstant中的me.setDevMode(…)配置。
特别注意:sql管理模块使用的模板引擎并非在 configEngine(Engine me)配置,因此在对其配置 shared method、directive 等扩展时需要使用 activeRecordPlugin.getEngine() 得到用于 sql 管理功能的 Engine 对象,然后对该 Engine 对象进行配置。
2、#sql 指令
通过#sql指令可以定义sql语句,如下是代码示例:
- #sql("findGirl")
- select * from girl where age > ? and age < ? and weight < 50
- #end
上例通过 #sql 指令在模板文件中定义了key值为“findGirl”的sql语句,在java 代码中的获取方式如下:
- String sql = Db.getSql("findGirl");
- Db.find(sql, 16, 23);
上例中第一行代码通过Db.getSql()方法获取到定义好的sql语句,第二行代码直接将sql用于数据库查询。
此外,还可以通过Model.getSql(key)方法来获取sql语句,功能与Db.getSql(key)基本一样,唯一不同的是为多数据源分别配置了sql模板的场景:
Model.getSql()在自身所对应的ActiveRecordPlugin的sql模板中去取sql
Db.getSql()在主ActiveRecordPlugin的sql模板中去取sql
可通过Db.use(…).getSql(…) 实现Model.getSql()相同功能
也就是说,多数据源之下,可为不同的ActiveRecordPlugin对象分别去配置sql模板,有利于模块化管理。
3、#para 指令
para 指令用于生成 sql 中的问号占位符以及问号占位符所对应的参数值,两者分别保存在 SqlPara对象的 sql 和 paraList 属性之中。
para指令支持两种用法,一种是传入 int型常量参数的用法,如下示例展示的是int型常量参数的用法:
- #sql("findGirl")
- select * from girl where age > #para(0) and weight < #para(1)
- #end
上例代码中两个 #para 指令,传入了两个 int 型常量参数,所对应的java后端代码必须调用getSqlPara(String key, Object… paras),如下是代码示例:
- SqlPara sqlPara = Db.getSqlPara("findGirl", 18, 50);
- Db.find(sqlPara);
以上第一行代码中的 18 与 50 这两个参数,分别被前面 #sql 指令中定义的 #para(0) 与 #para(1) 所使用。
Db.getSqlPara(String key, Object… paras) 方法的第二个参数 Object… paras,在传入实际参数时,下标值从 0 开始算起与 #para(int) 指令中使用的 int 型常量相对应。
para 指令的另一种用法是传入除了 int 型常量以外的任意类型参数(注意:两种用法处在同一个 #sql 指令之中时只能选择其中一种),如下是代码示例:
- #sql("findGirl")
- select * from girl where age > #para(age) and weight < #para(weight)
- #end
与上例模板文件配套的java代码如下所示:
- Kv cond = Kv.by("age", 18).set("weight", 50);
- SqlPara sp = Db.getSqlPara("findGirl", cond);
- Db.find(sp);
上例代码获取到的SqlPara对象sp中封装的sql为:select * from girl where age > ? and weight < ?,封装的与sql问号占位符次序一致的参数列表值为:[18, 50]。
以上两个示例,获取到的SqlPara对象中的值完全一样。其中的sql值都为:select from girl where age > ? and weight < *?,其中的参数列表值也都为 [18、50]。不同的是 #para 用法不同,以及它们对应的java代码传参方式不同,前者传入的是Object… paras参数,后者是Map data参数。
切记: #para 指令所到之处永远是生成一个问号占位符,并不是参数的值,参数值被生成在了SqlPara对象的paraList属性之中,通过sqlPara.getPara()可获取。如果想生成参数值用一下模板输出指令即可:#(value)
极其重要的通用技巧:如果某些数据库操作API不支持SqlPara参数,而只支持String sql和Object… paras这两个参数,可以这样来用:method(sqlPara.getSql(), sqlPara.getPara())。这样就可以让所有这类API都能用上Sql管理功能。
加餐:有些同学希望在 sql 文件中获取getSqlPara(String, Object… paras) 方法传入的paras参数,可以通过表达式 PARA_ARRAY[index] 来获取到下标为index的参数值。
由于经常有人问到 mysql 数据库 sql 语句的 like 子句用法,补充如下示例:
- #sql("search")
- select * from article where title like concat('%', #para(title), '%')
- #end
以上示例的like用法完全是 JDBC 决定的,JFinal仅仅是生成了如下sql而已:
select * from article where title like concat('%', ?, '%'),也就是仅仅将 #para(title) 替换生成为一个问号占位 ”?” 而已。
4、#namespace 指令
namespace 指令为 sql 语句指定命名空间,不同的命名空间可以让#sql指令使用相同的key值去定义sql,有利于模块化管理,如下所示:
- #namespace("japan")
- #sql("findGirl")
- select * from girl where age > ? and age < ? and weight < 50
- #end
- #end
上面代码指定了namespace为”japan”,在使用的时候,只需要在key前面添加namesapce值前缀 + 句点符 + key即可:
- getSql("japan.findGirl");
5、分页用法
Sql管理实现分页功能,在使用#sql定义sql时,与普通查询完全一样,不需要使用额外的指令,在java代码中使用getSqlPara得到SqlPara对象以后,直接扔给Db或者Model的paginate方法就打完收工了,以下是代码示例:
- SqlPara sqlPara = Db.getSqlPara("findGirl", 18, 50);
- Db.paginate(1, 10, sqlPara);
如以上代码所示,将sqlPara对象直接用于paginate方法即可,而#sql定义与普通的非分页sql定义完全相同。
传统而平庸的sql管理框架实现分页功能额外引入很多无聊的概念,例如需要引入page指令、orderBy指令、select指令或者各种tag等无聊的东西,徒增很多学习成本,浪费广大开发者的生命。
6、高级用法
除了#sql、#para、#namespace之外,还可以使用JFinal Template Engine中所有存在的指令,生成复杂条件的sql语句,以下是相对灵活的示例:
- #sql("find")
- select * from girl
- #for(x : cond)
- #(for.first ? "where": "and") #(x.key) #para(x.value)
- #end
- #end
以上代码#for指令对Map类型的cond参数进行迭代,动态生成自由的查询条件。上图中的三元表达式表示在第一次迭代时生成 where,后续则生成 and 。#(x.key) 输出参数 key 值,#para(x.value) 输出一个问号占位符,以及将参数 value 值输出到 SqlPara.paraList 中去。
以上sql 模板所对应的 java 代码如下:
- Kv cond = Kv.by("age > ", 16).set("sex = ", "female");
- SqlPara sp = Db.getSqlPara("find", Kv.by("cond", cond));
- Db.find(sp);
以上第一行代码是 JFinal 独创的参数带有比较运算符的用法,可以同时生成sql查询条件名称、条件运算符号、参数列表,一石三鸟。甚至可以将此法用于 and or not再搭配一个 LinkedHashMap 生成更加灵活的逻辑组合条件sql。
更加好玩的用法,可以用jfinal 模板引擎的 #define 指令将常用的 sql 定义成通用的模板函数,以便消除重复性 sql 代码。总之,利用 #sql、#para、#namespace 这三个指令再结合模板引擎已有指令自由组合,可非常简洁地实现极为强大的 sql管理功能。
注意:以上例子中的Kv是 JFinal 提供的用户体验更好的 Map 实现,使用任意的 Map 实现都可以,不限定为Kv。