ActiveRecord(下)
Generator与JavaBean
- 在此处创建生成器
- ActiveRecord 模块的 com.jfinal.plugin.activerecord.generator 包下,提供了一个 Generator 工具类,可自动生成 Model、BaseModel、MappingKit、DataDictionary 四类文件。
- 使用生成器通常只需配置Generator的四个参数即可,以下是具体使用示例:
// base model 所使用的包名 String baseModelPkg = "model.base"; // base model 文件保存路径 String baseModelDir = PathKit.getWebRootPath() + "/../src/model/base"; // model 所使用的包名 String modelPkg = "model"; // model 文件保存路径 String modelDir = baseModelDir + "/.."; Generator gernerator = new Generator(dataSource, baseModelPkg, baseModelDir, modelPkg, modelDir); // 在 getter、setter 方法上生成字段备注内容 gernerator.setGenerateRemarks(true); gernerator.generate();
Db + Record模式
- 常见用法如下:
// 创建name属性为James,age属性为25的record对象并添加到数据库 Record user = new Record().set("name", "James").set("age", 25); Db.save("user", user); // 删除id值为25的user表中的记录 Db.deleteById("user", 25); // 查询id值为25的Record将其name属性改为James并更新到数据库 user = Db.findById("user", 25).set("name", "James"); Db.update("user", user); // 获取user的name属性 String userName = user.getStr("name"); // 获取user的age属性 Integer userAge = user.getInt("age"); // 查询所有年龄大于18岁的user List<Record> users = Db.find("select * from user where age > 18"); // 分页查询年龄大于18的user,当前页号为1,每页10个user Page<Record> userPage = Db.paginate(1, 10, "select *", "from user where age > ?", 18);
- paginate 分页
- Model 与 Db 中提供了最常用的分页API:paginate(int pageNumber, int pageSize, String select, String sqlExceptSelect, Object… paras)
- 其中的参数含义分别为:当前页的页号、每页数据条数、sql语句的select部分、sql语句除了select以外的部分、查询参数。绝大多数情况下使用这个API即可。以下是使用示例:
dao.paginate(1, 10, "select *", "from girl where age > ? and weight < ?", 18, 50);
- 若需要分组,以下是代码示例:
dao.paginate(1, 10, true, "select *", "from girl where age > ? group by age", 18);
- 以上代码中 sql 的最外层有一个 group by age,所以第三个参数 isGroupBySql 要传入 true 值。
- 如果是嵌套型sql,但是 group by 不在最外层,那么第三个参数必须为 false,例如:select * from (select x from t group by y) as temp。
Dialect多数据库支持
- 目前ActiveRecordPlugin提供了MysqlDialect、OracleDialect、PostgresqlDialect、SqlServerDialect、Sqlite3Dialect、AnsiSqlDialect实现类。MysqlDialect与OracleDialect分别实现对Mysql与Oracle的支持,AnsiSqlDialect实现对遵守ANSI SQL数据库的支持。以下是数据库Dialect的配置代码:
public class DemoConfig extends JFinalConfig { public void configPlugin(Plugins me) { ActiveRecordPlugin arp = new ActiveRecordPlugin(…); me.add(arp); // 配置Postgresql方言 arp.setDialect(new PostgresqlDialect()); } }
调用存储过程
- 使用工具类 Db 可以很方便调用存储过程,以下是代码示例:
Db.execute((connection) -> { CallableStatement cs = connection.prepareCall(...); cs.setObject(1, ...); cs.setObject(2, ...); cs.execute(); cs.close(); return cs.getObject(1); });
- MySQL 之下还可以使用更简单的方式调用存储过程:
// 调用存储过程,查询 salary 表 Record first = Db.findFirst("CALL FindSalary (1,\"201901\")"); // 调用存储过程,插入 salary 表 int update2 = Db.update("CALL InsertSalary (3, 123)"); // 调用存储过程,更新 salary 表 int update = Db.update("CALL UpdateSalary (3, 99999)"); // 调用存储过程,删除 salary 表 int delete = Db.delete("CALL DeleteSalary(3)");
Enjoy 模板引擎
引擎配置
- 应用于 Controller.render(String) 的 Engine 对象的配置在 configEngine(Engine me) 中进行:
public void configEngine(Engine me) { // devMode 配置为 true,将支持模板实时热加载 me.setDevMode(true); }
- 应用于 SQL 管理的 Engine 对象的配置在 configPlugin(Plugins me) 中进行:
public void configPlugin(Plugins me) { ActiveRecordPlugin arp = new ActiveRecordPlugin(...); Engine engine = arp.getEngine(); // 上面的代码获取到了用于 sql 管理功能的 Engine 对象,接着就可以开始配置了 engine.setToClassPathSourceFactory(); engine.addSharedMethod(new StrKit()); me.add(arp); }
- 表达式
-
属性访问
- 由于模板引擎的属性取值表达式极为常用,所以对其在用户体验上进行了符合直觉的扩展,field 表达式取值优先次序,以
user.name
为例- 如果 user.getName() 存在,则优先调用
- 如果 user 具有 public 修饰过的name 属性,则取
user.name
属性值(注意:jfinal 4.0 之前这条规则的优先级最低) - 如果 user 为 Model 子类,则调用
user.get("name")
- 如果 user 为 Record,则调用
user.get("name")
- 如果 user 为 Map,则调用
user.get("name")
- 由于模板引擎的属性取值表达式极为常用,所以对其在用户体验上进行了符合直觉的扩展,field 表达式取值优先次序,以
-
方法调用
- 模板引擎被设计成与 java 直接打通,可以在模板中直接调用对象上的任何public方法,使用规则与java中调用方式保持一致,以下代码示例:
-
静态属性访问
- 类名加双冒号再加静态属性名即为静态属性访问表达式,这里的属性必须是public static修饰过的才可以被访问。此外,这里的静态属性并非要求为final修饰。
- 如果某个静态属性要被经常使用,建议通过 addSharedObject(…) 将其配置成共享对象,然后通过 field 表达式来引用,从而节省代码,例如先配置 shared object:
public void configEngine(Engine me) { me.addSharedObejct("Account", new Account()); }
- 然后在模板中就可以使用 field 表达式来代替原有的静态属性访问表达式了:
#if(x.status == Account.STATUS_LOCK_ID) <span>(账号已锁定)</span> #end
- 使用方式与前面的静态属性访问保持一致,仅仅是将静态属性名换成静态方法名,并且后面多一对小括号与参数:类名 + :: + 方法名(参数)。静态方法调用支持可变参数。与静态属性相同,被调用的方法需要使用public static 修饰才可访问。
-
空合并安全取值调用操作符
- 符号
??
,示例:seoTitle ?? "JFinal 社区" object.field ?? object.method() ??
- 以上第一行代码表示在seoTitle 值为null时整个表达式取后面表达式的值。而第二行代码表示对object.field进行空安全(Null Safe)属性取值,即在object为null时表达式不报异常,并且值为null。
- 第三行代码与第二行代码类似,仅仅是属性取值变成了方法调用,并称之为空安全(Null Safe)方法调用,表达式在object为null时不报异常,其值也为null。
- 符号
-
单引号字符串
- 针对Template Engine 经常用于html的应用场景,添加了单引号字符串支持,以下是代码示例:
<a href="/" class="#(menu == 'index' ? 'current' : 'normal')" 首页 </a>
- 以上代码中的三元表达式中有三处使用了单引号字符串,好处是可以与最外层的双引号协同工作,也可以反过来,最外层用单引号字符串,而内层表达式用双引号字符串。
- 这个设计非常有利于在模板文件中已有的双引号或单引号内容之中书写字符串表达式。
- 针对Template Engine 经常用于html的应用场景,添加了单引号字符串支持,以下是代码示例:
-
相等于不相等
- 相等与不等比较表达式增强
- 相等不等表达式 == 与 != 会对左右表达式进行left.equals(right)比较操作,所以可以对字符串进行直接比较,如下所示:
#if(nickName == "james") ... #end
- 注意:Controller.keepPara(…) 方法会将任何数据转换成String后传递到view层,所以原本可以用相等表达式比较的两个Integer型数据,在keepPara(…)后变得不可比较,因为变为了String与Integer型的比较。解决方法见本章的Extionsion Method小节。
-
布尔表达式
- 布尔表达式增强
- 布尔表达式在原有java基础之下进行了增强,可以减少代码输入量,具体规则自上而下优先应用如下列表:
- null 返回 false
- boolean 类型,原值返回
- String、StringBuilder等一切继承自 CharSequence 类的对象,返回 length > 0
- 其它返回 true
-
Map 定义表达式
- map的定义使用一对大括号,每个元素以key : value的形式定义,多个元素之间用逗号分隔。
- 如果使用 map[k1] 来取值,则会对 k1 标识符先求值,再求map[k1值结果],因此如果不需要求职,直接使用 map[“k1”] 这样的形式来取值。
-
数组定义表达式
- 数组定义表达式的初始化元素除了可以使用常量值以外,还可以使用任意的表达式,包括变量、方法调用返回值
-
范围数组定义表达式
- 表达式 [1…10] 定义了一个范围数组,其值为从1到10的整数数组。
-
指令
- #if、#for、#switch、#set(只接受赋值表达式)、#include、#define、#(…) (输出指令)
- for指令:
- 示例:
#for(x : list) #(x.field) #end // 对 Map 进行迭代 #for(x : map) #(x.key) #(x.value) #end
- for指令支持的所有状态值如下示例
#for(x : listAaa) #(for.size) 被迭代对象的 size 值 #(for.index) 从 0 开始的下标值 #(for.count) 从 1 开始的记数值 #(for.first) 是否为第一次迭代 #(for.last) 是否为最后一次迭代 #(for.odd) 是否为奇数次迭代 #(for.even) 是否为偶数次迭代 #(for.outer) 引用上层 #for 指令状态 #end
- for 指令还支持 #else 分支语句,在for指令迭代次数为0时,将执行 #else 分支内部的语句
- 示例:
- #switch 指令
- #case 分支指令支持以逗号分隔的多个参数,#case 指令参数还可以是任意表达式。
- #include 指令
- include指令用于将外部模板内容包含进来,被包含的内容会被解析成为当前模板中的一部分进行使用
- #include 指令第一个参数必须为 String 常量,当以 ”/” 打头时将以 baseTemplatePath 为相对路径去找文件,否则将以使用 #include 指令的当前模板的路径为相对路径去找文件。
- baseTemplatePath 可以在 configEngine(Engine me) 中通过 me.setBaseTemplatePath(…) 进行配置。
- #date 指令
- date指令用于格式化输出日期型数据,包括Date、Timestamp等一切继承自Date类的对象的输出,使用方式极其简单:
#date(account.createAt) #date(account.createAt, "yyyy-MM-dd HH:mm:ss")
- date指令用于格式化输出日期型数据,包括Date、Timestamp等一切继承自Date类的对象的输出,使用方式极其简单:
- #number 指令
- number 指令用于格式化输出数字型数据,包括 Double、Float、Integer、Long、BigDecimal 等一切继承自Number类的对象的输出,使用方式依然极其简单,使用DecimalFormat方式格式化数据。
#number(3.1415926, "#.##") #number(0.9518, "#.##%") #number(300000, "光速为每秒,### 公里。")
- #escape 指令
- escape 指令用于 html 安全转义输出,可以消除 XSS 攻击。
#escape(blog.content)
- escape 指令用于 html 安全转义输出,可以消除 XSS 攻击。
- for指令:
注释
### 这里是单行注释
#--
这里是多行注释的第一行
这里是多行注释的第二行
--#
原样输出
- 原样输出是指不被解析,而仅仅当成纯文本的内容区块,格式为
#[[......]]#
。
Shared Method 扩展
public void configEngine(Engine me) {
me.addSharedMethod(new com.jfinal.kit.StrKit());
}
- 以上代码已将StrKit类中所有的public方法添加为shared method,添加完成以后便可以直接在模板中使用。
- Enjoy 模板引擎默认配置添加了isEmpty(…) 与 notEmpty(…) 两个方法可以使用.
- isEmpty(…) 用来判断 Collection、Map、数组、Iterator、Iterable 类型对象中的元素个数是否为 0
Shared Object扩展
public void configEngine(Engine me) {
me.addSharedObject("RESOURCE_HOST", "http://res.jfinal.com");
me.addSharedObject("sk", new com.jfinal.kit.StrKit());
}
/*
以上代码中的第二行,添加了一个名为RESOURCE_HOST的共享对象,
而第三行代码添加了一个名为sk的共享对象,以下是在模板中的使用例子:
*/
<img src="#(RESOURCE_HOST)/img/girl.jpg" />
#if(sk.isBlank(title))
...
#end
- 由于对象被全局共享,所以需要注意线程安全问题,尽量只共享常量以及无状态对象。
Extension Method扩展
- JFinal Template Engine 默认已经为String、Integer、Long、Float、Double、Short、Byte 这七个基本的 java 类型,添加了toInt()、toLong()、toFloat()、toDouble()、toBoolean()、toShort()、toByte() 七个extension method。
EhCachePlugin
- EhCachePlugin是JFinal集成的缓存插件,通过使用EhCachePlugin可以提高系统的并发访问速度。
配置
public class DemoConfig extends JFinalConfig {
public void configPlugin(Plugins me) {
me.add(new EhCachePlugin());
}
}
CacheInterceptor
- CacheInterceptor可以将action所需数据全部缓存起来,下次请求到来时如果cache存在则直接使用数据并render,而不会去调用action。此用法可使action完全不受cache相关代码所污染,即插即用
- CacheInterceptor可以与CacheName 注解配合使用,以此来取代默认的actionKey作为cacheName,以下是示例代码:
以上用法需要在ehcache.xml中配置名为blogList的cache如:@Before(CacheInterceptor.class) @CacheName("blogList") public void list() { List<Blog> blogList = Blog.dao.find("select * from blog"); setAttr("blogList", blogList); render("blog.html"); }
<cache name="blogList" …>
- 清除缓存
- EvictInterceptor可以根据CacheName注解自动清除缓存,示例如下:
@Before(EvictInterceptor.class) @CacheName("blogList, hotBlogList") // 逗号分隔多个 cacheName public void update() { ... }
- EvictInterceptor可以根据CacheName注解自动清除缓存,示例如下:
- CacheKit
- CacheKit是缓存操作工具类,以下是示例代码:
public void list() { List<Blog> blogList = CacheKit.get("blog", "blogList"); if (blogList == null) { blogList = Blog.dao.find("select * from blog"); CacheKit.put("blog", "blogList", blogList); } setAttr("blogList", blogList); render("blog.html"); }
- get(String cacheName, Object key)
- put(String cacheName, Object key, Object value)
- 参数cacheName与ehcache.xml中的<cache name=“blog” …>name属性值对应;参数key是指取值用到的key;参数value是被缓存的数据。
- 以下代码是CacheKit中重载的CacheKit.get(String, String, IDataLoader)方法使用示例:
public void list() { List<Blog> blogList = CacheKit.get("blog", "blogList", new IDataLoader(){ public Object load() { return Blog.dao.find("select * from blog"); }}); setAttr("blogList", blogList); render("blog.html"); }
- CacheKit.get方法提供了一个IDataLoader接口,该接口中的load()方法在缓存值不存在时才会被调用。该方法的具体操作流程是:首先以cacheName=blog以及key=blogList为参数去缓存取数据,如果缓存中数据存在就直接返回该数据,不存在则调用IDataLoader.load()方法来获取数据。
- CacheKit是缓存操作工具类,以下是示例代码:
Validator
- Validator自身实现了Interceptor接口,所以它也是一个拦截器,配置方式与拦截器完全一样。
public class LoginValidator extends Validator { protected void validate(Controller c) { validateRequiredString("name", "nameMsg", "请输入用户名"); validateRequiredString("pass", "passMsg", "请输入密码"); } protected void handleError(Controller c) { c.keepPara("name"); c.render("login.html"); } }
- 如果传递过来的是 model 对象,可以使用keepModel(…) 方法来保持住用户输入过的数据。同理,如果传递过来的是传统 java bean 对象,可以使用 keepBean(…) 方法来保持住用户输入过的数据。
- setRet(…) 与 getRet()
public class LoginValidator extends Validator { protected void validate(Controller c) { setRet(Ret.fail()); validateRequired("userName", "msg", "邮箱不能为空"); validateEmail("userName", "msg", "邮箱格式不正确"); validateRequired("password", "msg", "密码不能为空"); validateCaptcha("captcha", "msg", "验证码不正确"); } protected void handleError(Controller c) { c.renderJson(getRet()); } }
- Validator配置方式与拦截器完全一样,见如下代码:
public class UserController extends Controller { @Before(LoginValidator.class) // 配置方式与拦截器完全一样 public void login() { } }
JSON转换
- final 官方提供了 Json 抽象类的三个实现:JFinalJson、FastJson、Jackson,如果不进行配置,那么默认使用 JFinalJson 实现,指定为其它实现需要在 configConstant 进行如下配置:
public void configConstant(Constants me) { me.setJsonFactory(new FastJsonFactory()); }
- 此外,jfinal 官方还提供了 MixedJson、MixedJsonFactory 实现,这个实现让转 json string 时使用 JFinalJson,反向转成对象则使用 FastJson。
- 使用 MixedJson 封装时需要添加 FastJson 封装的依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.55</version> </dependency>
- JSON转换用法
// 在 Controller 中使用 renderJson 进行 json 转换,并渲染给客户端
renderJson();
renderJson(key, object);
renderJson(new String[]{...});
// 使用 JsonKit 工具类进行 json 转换
JsonKit.toJson(...);
JsonKit.parse(...);
以上内容来自官方文档JFinal开发教程,个人仅做简单整理。