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管理功能示例代码如下:

  1. ActiveRecordPlugin arp = new ActiveRecordPlugin(druidPlugin);
  2. arp.getEngine().setSourceFactory(new ClassPathSourceFactory());
  3. arp.addSqlTemplate("all.sql");
  4. _MappingKit.mapping(arp);
  5. 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语句,如下是代码示例:

  1. #sql("findGirl")
  2. select * from girl where age > ? and age < ? and weight < 50
  3. #end

上例通过 #sql 指令在模板文件中定义了key值为“findGirl”的sql语句,在java 代码中的获取方式如下:

  1. String sql = Db.getSql("findGirl");
  2. 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型常量参数的用法:

  1. #sql("findGirl")
  2. select * from girl where age > #para(0) and weight < #para(1)
  3. #end

上例代码中两个 #para 指令,传入了两个 int 型常量参数,所对应的java后端代码必须调用getSqlPara(String key, Object… paras),如下是代码示例:

  1. SqlPara sqlPara = Db.getSqlPara("findGirl", 18, 50);
  2. 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 指令之中时只能选择其中一种),如下是代码示例:

  1. #sql("findGirl")
  2. select * from girl where age > #para(age) and weight < #para(weight)
  3. #end

与上例模板文件配套的java代码如下所示:

  1. Kv cond = Kv.by("age", 18).set("weight", 50);
  2. SqlPara sp = Db.getSqlPara("findGirl", cond);
  3. 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 子句用法,补充如下示例:

  1. #sql("search")
  2. select * from article where title like concat('%', #para(title), '%')
  3. #end

以上示例的like用法完全是 JDBC 决定的,JFinal仅仅是生成了如下sql而已:

select * from article where title like concat('%', ?, '%'),也就是仅仅将 #para(title) 替换生成为一个问号占位 ”?” 而已。

4、#namespace 指令

namespace 指令为 sql 语句指定命名空间,不同的命名空间可以让#sql指令使用相同的key值去定义sql,有利于模块化管理,如下所示:

  1. #namespace("japan")
  2. #sql("findGirl")
  3. select * from girl where age > ? and age < ? and weight < 50
  4. #end
  5. #end

上面代码指定了namespace为”japan”,在使用的时候,只需要在key前面添加namesapce值前缀 + 句点符 + key即可:

  1. getSql("japan.findGirl");

5、分页用法

Sql管理实现分页功能,在使用#sql定义sql时,与普通查询完全一样,不需要使用额外的指令,在java代码中使用getSqlPara得到SqlPara对象以后,直接扔给Db或者Model的paginate方法就打完收工了,以下是代码示例:

  1. SqlPara sqlPara = Db.getSqlPara("findGirl", 18, 50);
  2. Db.paginate(1, 10, sqlPara);

如以上代码所示,将sqlPara对象直接用于paginate方法即可,而#sql定义与普通的非分页sql定义完全相同。

传统而平庸的sql管理框架实现分页功能额外引入很多无聊的概念,例如需要引入page指令、orderBy指令、select指令或者各种tag等无聊的东西,徒增很多学习成本,浪费广大开发者的生命。

6、高级用法

除了#sql、#para、#namespace之外,还可以使用JFinal Template Engine中所有存在的指令,生成复杂条件的sql语句,以下是相对灵活的示例:

  1. #sql("find")
  2. select * from girl
  3. #for(x : cond)
  4. #(for.first ? "where": "and") #(x.key) #para(x.value)
  5. #end
  6. #end

以上代码#for指令对Map类型的cond参数进行迭代,动态生成自由的查询条件。上图中的三元表达式表示在第一次迭代时生成 where,后续则生成 and 。#(x.key) 输出参数 key 值,#para(x.value) 输出一个问号占位符,以及将参数 value 值输出到 SqlPara.paraList 中去。

以上sql 模板所对应的 java 代码如下:

  1. Kv cond = Kv.by("age > ", 16).set("sex = ", "female");
  2. SqlPara sp = Db.getSqlPara("find", Kv.by("cond", cond));
  3. Db.find(sp);

以上第一行代码是 JFinal 独创的参数带有比较运算符的用法,可以同时生成sql查询条件名称、条件运算符号、参数列表,一石三鸟。甚至可以将此法用于 and or not再搭配一个 LinkedHashMap 生成更加灵活的逻辑组合条件sql。

更加好玩的用法,可以用jfinal 模板引擎的 #define 指令将常用的 sql 定义成通用的模板函数,以便消除重复性 sql 代码。总之,利用 #sql、#para、#namespace 这三个指令再结合模板引擎已有指令自由组合,可非常简洁地实现极为强大的 sql管理功能。

注意:以上例子中的Kv是 JFinal 提供的用户体验更好的 Map 实现,使用任意的 Map 实现都可以,不限定为Kv。

< 5.12 Oracle支持

5.14 多数据源支持 >