注:我这里只是记了下实际应用,很多细节上的东西没写进来,想学习JFinal的话,可以一边参考JFinal官方文档一边参考我这个,当然,水平有限,只做了一些简单的入门级代码
Jfinal推荐使用WebRoot\WEB-INF\classes放class文件,
于是创建项目的时候Default output folder位置改成上面,然后Content directory要与此对应。,当然也可以用tomcat来使用,跟SSH的新建dynamic Web Project一样新建出来就可;
添加JFinal的Controller过滤器
<filter>
<filter-name>jfinalfilter-name>
<filter-class>com.jfinal.core.JFinalFilterfilter-class>
<init-param>
<param-name>configClassparam-name>
<param-value>demo.DemoConfigparam-value>
init-param>
filter>
<filter-mapping>
<filter-name>jfinalfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
创建配置文件,是个java文件
package demo;
import com.jfinal.config.*;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.c3p0.C3p0Plugin;
import com.jfinal.render.ViewType;
public class DemoConfig extends JFinalConfig{
/**
此方法用来配置 JFinal 常量值, 如开发模式常量 devMode 的配置, 默认视图类型 ViewType的配置
JFinal 会对每次请求输出报告,如输出本次请求的 Controller、Method 以及请求所携带的参数。JFinal 支持 JSP、FreeMarker、Velocity 三种常用视图。
*/
@Override
public void configConstant(Constants me) {
me.setDevMode(true);
//设置运行在开发模式下的默认视图类型为FREE_MARKER
me.setViewType(ViewType.FREE_MARKER);
}
/**
* 此方法用来配置 JFinal 访问路由(路径),如下代码配置了将”/hello”映射到 HelloController 这个控制 器 , 通 过 以 下 的 配 置 , http://localhost/hello 将 访 问 HelloController.index() 方 法 , 而http://localhost/hello/methodName 将访问到 HelloController.methodName()方法。
*
*/
@Override
public void configRoute(Routes me) {
//add表示添加了一个控制器,路径为/,进入该控制器将默认访问该控制器的index方法,HelloControl为处理该请求路径的类。
//加入要访问该控制器下的其他请求,那么在输入http输入/hello/test,就访问到该控制器中的test方法
me.add("/",HelloControl.class);
// me.add("/hello/test",HelloControl.class);
}
/**
* 此方法用来配置 JFinal 的 Plugin, 如下代码配置了 C3p0 数据库连接池插件与 ActiveRecord
数据库访问插件。通过以下的配置,可以在应用中使用 ActiveRecord 非常方便地操作数据库
*
*/
@Override
public void configPlugin(Plugins me) {
loadPropertyFile("config.txt");
C3p0Plugin c3p0Plugin = new C3p0Plugin(getProperty("jdbcURL"),
getProperty("user"),getProperty("password")
);
me.add(c3p0Plugin);
ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin);
//显示sql语句
arp.setShowSql(true)
me.add(arp);
//
//添加数据库与Model的关系映射,User需要继承Model<>类
arp.addMapping("user", User.class);
}
@Override
public void configInterceptor(Interceptors me) {}
/**
*
* 此方法用来配置JFinal的Handler, 如下代码配置了名为ResourceHandler的处理器, Handler
可以接管所有 web 请求,并对应用拥有完全的控制权,可以很方便地实现更高层的功能性扩
展
*/
@Override
public void configHandler(Handlers me) {
}
}
package demo;
import com.jfinal.core.Controller;
public class HelloControl extends Controller {
public void index(){
String msg = "Welcome To JFinal World";
//将信息传递到页面,页面中读取代码为:
//
//${(helloworld)!''}
setAttr("helloworld", msg);
//使用FreeMarker渲染页面
renderFreeMarker("helloworld.html");
}
//该注解是直接映射,意思测试的时候使用localhost/test就会访问到该路径,当然,服务器端口使用的80
@ActionKey("test")
public void test(){
renderFreeMarker("/helloworld.html");
}
}
有个小技巧,在html页面中,可以使用
<#include “xxx.html”>
可以将xxx.html里面的东西包含到你需要的页面,例如修改和删除都使用同一个页面的情况下,就可以用的到。
数据源配置即配置数据库连接,在这里使用的mysql,以及使用外置文件配置相关连接参数,url,username等;
在jfinal中,这种配置类似一种插件,可插拔,所以放在了配置未见中的
configPlugin(Plugins me方法中)
/**
* 此方法用来配置 JFinal 的 Plugin, 如下代码配置了 C3p0 数据库连接池插件与 ActiveRecord
数据库访问插件。通过以下的配置,可以在应用中使用 ActiveRecord 非常方便地操作数据库
*
*/
@Override
public void configPlugin(Plugins me) {
//加载配置文件
loadPropertyFile("config.txt");
//C3P0数据源的初始化
C3p0Plugin c3p0Plugin = new C3p0Plugin(getProperty("jdbcURL"),
getProperty("user"),getProperty("password")
);
me.add(c3p0Plugin);
ActiveRecordPlugin arp = new ActiveRecordPlugin(c3p0Plugin);
//可插拔,添加数据库和对象之间的数据源
me.add(arp);
//映射数据库,user是表名,表默认主键是id,后面参数是实体,将实体和表映射,注意,User继承了Jfinal的Model类,是个泛型类
arp.addMapping("user", User.class);
}
Model只需要继承一个类,不用设置属性,属性是从数据库里面映射过来,数据库的列名是什么,这个model对应的属性就是什么
package demo;
import com.jfinal.plugin.activerecord.Model;
public class User extends Model<User> {
/**
*
*/
private static final long serialVersionUID = 1L;
public static User dao = new User();
如果使用get方法的RESTFul风格,那么有可能出现问题,在使用这样的 getParaToInt(“id”)的时候
Jfinal中接收表单参数的方法有几种,一种是getPara(“表单name”),一种是使用对象来接收参数,类似Spring MVC的控制器,
在控制器中某表单提交去向对应的方法,我这给的是test,
public void test(){
//这个是最简单的方法,通过getPara
// getPara("表单名");
//接受传过来的参数,刚好是某个对象的属性,使用getModel来取得表单参数,getModel有2种重载,一种是单个参数,一种是两个参数,单个参数的如下,在表单域里面输入框的name,例如我这使用的是User.class,那么在那里就写user.attribute,对象.属性的方法来作为name,在表单页面的输入框name为User,首字母小写就对应这个对象,
User user = getModel(User.class);
//第二种重载,getModel(User.class,"u"),就是起个别名,在表单里面的name就为u.attribute,就可以接收参数了。
User user1 = getModel(User.class,"u");
//取出user的某个属性,还有getInt,getDate等方法来获取对应类型的属性
System.out.println(user.getStr("name"));
//这个方法是渲染视图,上面讨论过。如何传数据从控制器到页面呢?使用setAttr("参数名",参数值)这个方法来将参数放到request域里面传递到页面
renderFreeMarker("/helloworld.html");
}
对象的属性是从数据库直接获取的,假如数据库列名是name,那么当我们在取name这个属性的时候,在页面,就直接${user.name}
package demo;
import com.jfinal.plugin.activerecord.Model;
public class User extends Model<User> {
private static final long serialVersionUID = 1L;
public static User dao = new User();
}
使用freemaker渲染页面,html页面如下书写,接收服务器传过来的数据(未分页版本)
其中 userList是从服务器返回来的数据,是一个集合,这里做的是遍历。list表示遍历这个集合
<#list userList as user>
<tr>
<td>
${(user.id)!''}
td>
<td>
${(user.name)!''}
td>
<td>
${(user.address)!''}
td>
<td>
${(user.phone)!''}
td>
<td>
这个是查询单条的访问路径
<a href="/userid/${(user.id)!''}">查看a>
td>
tr>
#list>
分页查询输出当前页码,总记录数,总页数;
总共${userList.totalRow}条记录,当前第${pageNum}页,总共${userList.totalPage}页
<#--一个简单的分页控制器,顺便用了下freemaker的if使用,freemaker要使用#才能注释,注意,条件判断之后不要有空格,因为不会自动过滤空格-->
<p><a href="/userPage/1">首页a>
<a href="/userPage/<#if pageNum == 1>1
<#else>${pageNum-1}
#if>">上一页a> &ebsp;
<a href="/userPage/<#if pageNum == userList.totalPage>${userList.totalPage}
<#else>${pageNum+1}
#if>">下一页a>
<a href="/userPage/${userList.totalPage}">末页a>
p>
/**
查询所有
*/
public void user(){
String sql = "select * from user order by id desc";
setAttr("userList",User.dao.find(sql));
renderFreeMarker("/helloworld.html");
}
/**
* 查询单条记录
*/
public void userid(){
//截取id,注意,这种是RESTFul风格的获取get方式传参的手段,假如使用getParaToInt("id"),url为userid/1,的时候,获取不到值,那么这里也支持问号传参,userid?id=xxx,这样就可以使用getParaToInt("id")来取得id
int id = getParaToInt(0);
//根据id查询用户
User user = User.dao.findById(id);
//还可以这样写,下面参数第二个开始是一个Object... params,不定参数
User user = User.dao.findFirst("select * from user where id = ? ", id);
//显示查询到的用户信息
System.out.println(user);
}
/**
* 分页查询,Jfinal提供了方法
* paginate(pageNumber, pageSize, 需要查的映射(select *), sql语句,从from开始, 用以替换?的参数)
* 最后一个参数可要可不要,因为是不定参数。
*/
public void userPage(){
String sql = "from user";
int pageNum = getParaToInt("pageNo",1);
setAttr("pageNum", pageNum);
setAttr("userList", User.dao.paginate(pageNum, 2, "select *", sql));
renderFreeMarker("helloworld.html");
}
public void addUser() {
//从页面获取user的属性
User user = getModel(User.class);
//save()方法放返回一个布尔值,如果添加成功返回true,在这里我们添加成功的话就重定向到user列表页面,失败的话就渲染一个纯文本页面,renderText
boolean flag = user.save();
if (flag) {
redirect("/user/");
} else {
renderText("Sorry,有异常,插入失败");
}
}
public void deleteUser() {
//取得例如deleteUser/1-2-3中第一个参数1,加入使用getParaToInt(1);则取到的是第二个参数2
int id = getParaToInt(0);
boolean flag = User.dao.deleteById(id);
if (flag) {
redirect("/user/");
} else {
renderText("Sorry,有异常,删除失败");
}
}
<#list userList as user>
<tr>
<td>
${(user.id)!''}
td>
<td>
${(user.name)!''}
td>
<td>
${(user.address)!''}
td>
<td>
${(user.phone)!''}
td>
<td>
<a href="/deleteUser/${(user.id)!''}">删除a>
td>
tr>
#list>
页面其实跟添加页面差不多,只是action的时候带过去id,在这里是做了一个隐藏域,当然也可以在表单的action中updateUser/${(user.id)!”}这样来传递ID
/**
修改用户
*/
public void updateUser() {
User user = getModel(User.class);
boolean flag = user.update();
if (flag) {
redirect("/user/");
} else {
renderText("Sorry,有异常,修改失败");
}
}
拦截器:JFinal AOP的实现方式,拦截器并非线程安全,县城安全拦截器需要继承PrototypeInteceptor来实现
拦截器有三个级别,Global,Controller,Action,Global对所有Action进行拦截;Controller就是一整个控制器,包含了多个Action级, Action级就是具体到某个Action的拦截
Interceptor通过注解来实现,支持的注解为@Before(xxxInterceptor.class)
这个xxxInterceptor需要实现Interceptor接口
public class DemoActionInterceptor implements Interceptor {
@Override
public void intercept(Invocation inv) {
Controller c = inv.getController();
System.out.println("这个是拦截器在业务方法前执行的代码");
inv.invoke();//让被拦截的方法继续执行。
System.out.println("这个是拦截器在业务方法后执行的代码部分");
}
}
全局Interceptor的配置:在Config里面的configInterceptor中配置,而不使用注解,代码如下。
public void configInterceptor(Interceptors me) {
me.add(new DemoInterceptor());//定义一个全局拦截器
}
Controller级别Interceptor的配置:其实就是将Before放在类上,
@Before({DemoInterceptor.class,OtherInteceptor.class})
public class HelloControl extends Controller {
public void index() {}
}
Action级别的拦截器:其实就是放在Action上面,即Controller里面的某个方法
public class AuthInterceptor implements Interceptor {
public void intercept(ActionInvocation ai) {
Controller controller = ai.getController();
User loginUser = controller.getSessionAttr("loginUser");
if (loginUser != null && loginUser.canVisit(ai.getActionKey()))
ai.invoke();
else
controller.redirect("/login.html");
研究了3-4个小时,一直在纠结Interceptor和AOP,SSH中的AOP是放在了Service中的方法上,于是我按照同样的思路想在jfinal中搞个service层,然后给其加上AOP,后来发现不可行,报了这么个错误;
This method can only be used for action interception
意思这个方法只能用于Action拦截,深层意思就是只能用在控制器Controller上,而不能用在自己定义的Service上。
意味着jfinal的AOP都是实现在控制器级别的,例如这样:
@Before(DemoActionInterceptor.class)
public void test1(){
//这个enhance就是返回一个带了事务(@Before(Tx.class))的Service对象
UserService userService = enhance(UserService.class);//还可以上第二个参数,Tx.class,Inject拦截注入一个事务给service,就可以不用在service里面来Before了
User user = userService.login("老大","asd");
if(user!=null){
System.out.println("业务实体返回了一个DTO:" + user);
renderText("asd");
}else{
renderText("用户名或密码错误");
}
}
业务层代码:
//@Before(DemoActionInterceptor.class如果加了这句话会报运行时错误。
public class UserService {
@Before(Tx.class)
public User login(String username){
//调用dao,jfinal中model其实就是一个dao
User u = User.dao.findFirst("select * from user where name=?",username);
if(u != null && password.equals(u.getStr("password"))){
return u;}
else
return null;
}
}
实践表明,Interceptor不能单独的应用在S层,必须应用在C层,为什么这样呢,看了下Jfinal大大对于这个问题给的回复,恍然大悟,。以下是大大的设计思路
- AOP 本来就是独立出来的,业务层不需要知道 AOP 在哪里,AOP紧靠service或controller在本质上并无差别,如果要找差别的话:==紧靠service层做事务 AOP会让事务开启的时间稍晚一些,带来略微的性能提升,其实controller中的代码是简单的控制代码所耗性能对于业务层来说可以忽略不计,所以在 controller 上做声明式事务是jfinal权衡后最佳的选择。==
AOP 希望贴近 service 来做是理论化、学术化的诉求,通常软件开发是工程性的活动,理论化与学术化不经济也不实用。
So,我们的S层仅仅只需要事务支持,(事务支持其实可以简单理解成就是数据库操作的多次操作,然后事务提交,提交失败回滚),而不做AOP,AOP放在了Controller来进行操作,假如需要的话。
这个问题也是思考了半天,最后咨询得出,有两个方案,一个方案是放在Model里面,一个方案也是单独抽一个Service层出来,SSH的思路是S层调用dao对象,所以可以在Service中使用Model.dao来调用dao对象(Model.dao是一个静态的已经初始化号的Model对象,当然这个Model继承了Jfinal提供的Model
)
PS:没想到这么快就接触到了领域模型,充血模型,,我还以为我要在事务脚本摸爬滚打许久;
Jfinal中表关联映射有两种,一种是通过sql语句的联表查询来进行关联,什么inner join,left join之类的,跟sql查询差不多。
public void relation() {
String sql = "select b.*, u.user_name from blog b inner join user u on b.user_id=u.id where b.id=?";
Blog blog = Blog.dao.findFirst(sql, 123);
String name = blog.getStr("user_name");
}
另外一种就是Jfinal特有的
理解思路:加入有两个对象,一个User,一个Blog,一对多关系,一个User有多个Blog,一个Blog只有一个User。
思路即:查询方法直接写好,getUser(),传入Blog中的表示User的外键user_id来查询Blog对应的User;
在User中,用Blog.dao来查询,传入User的id来查询该User拥有的Blog集合
public class Blog extends Model<Blog>{
public static final Blog dao = new Blog();
public User getUser() {
return User.dao.findById(get("user_id"));
}
}
public class User extends Model<User>{
public static final User dao = new User();
public List getBlogs() {
return Blog.dao.find("select * from blog where user_id=?",
get("id"));
}
}
这个部分包含了Model以及Db+Record的模式来替代Model;
两者用法区别不大,唯一的区别,在于不需要建立实体类,例如,之前使用dao的时候会建立一个继承Model的model,然后内部实例化该对象来当dao使用,那么在使用Db+Record的时候就可以采用输入表名的手段来区别实体对象,就不用单独建立实体类;代码区别示例
这个是Model
//创建name属性为James,age属性为25的User对象并添加到数据库
new User().set("name", "James").set("age", 25).save();
//删除id值为25的User
User.dao.deleteById(25);
//查询id值为25的User将其name属性改为James并更新到数据库
User.dao.findById(25).set("name", "James").update();
//查询id值为25的user, 且仅仅取name与age两个字段的值
User user = User.dao.findById(25, "name, age");
//获取user的name属性
String userName = user.getStr("name");
//获取user的age属性
Integer userAge = user.getInt("age");
//查询所有年龄大于18岁的user
List users = User.dao.find("select * from user where age>18");
// 分页查询年龄大于18的user,当前页号为1,每页10个user
Page userPage = User.dao.paginate(1, 10, "select *", "from user where age > ?", 18);
这个是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);
// 查询id值为25的user, 且仅仅取name与age两个字段的值
user = Db.findById("user", 25, "name, age");
// 获取user的name属性
String userName = user.getStr("name");
// 获取user的age属性
Integer userAge = user.getInt("age");
// 查询所有年龄大于18岁的user
List users = Db.find("select * from user where age > 18");
// 分页查询年龄大于18的user,当前页号为1,每页10个user
Page userPage = Db.paginate(1, 10, "select *", "from user where age > ?", 18);
说到事务,其实底层就是把
jdbc的Connection conn.autoCommit(false)设置为false,
然后进行jdbc几个操作一起进行,捆绑进行,要不都完成成功,要不都失败,然后开始
Connection conn.commit()开始提交
,提交成功就都成功,失败就回滚,一般来个try catch,抓到异常后进行事务回滚,
session.rollback();事务回滚
编程式事务:
boolean succeed = Db.tx(new IAtom(){
public boolean run() throws SQLException {
//数据操作1
int count = Db.update("update account set cash = cash - ? where id = ?", 100, 123);
//数据操作2
int count2 = Db.update("update account set cash = cash + ? where id = ?", 100, 456);
return count == 1 && count2 == 1;
}});
声名式事务:
其实跟之前的事务差不多,来个注解
// 本例仅为示例, 并未严格考虑账户状态等业务逻辑
@Before(Tx.class)
public void trans_demo() {
// 获取转账金额
Integer transAmount = getParaToInt("transAmount");
// 获取转出账户id
Integer fromAccountId = getParaToInt("fromAccountId");
// 获取转入账户id
Integer toAccountId = getParaToInt("toAccountId");
// 转出操作
Db.update("update account set cash = cash - ? where id = ?",
transAmount, fromAccountId);
// 转入操作
Db.update("update account set cash = cash + ? where id = ?",
transAmount, toAccountId);
}
缓存就是什么,就是你经常要查的东西来放到缓存里,因为开启数据库是个代价比较高昂的操作,所以应用缓存来减少数据库开和关的次数来提升性能,类似连接池,空间换时间;
一般缓存里放的东西都是经常查但又不经常修改的数据,因为缓存可以设置缓存内容失效时间,可以相对也比较灵活。
在Jfinal中默认使用的Cache是EhCache,使用方法有几个地方
1:添加相关jar报,包括
- ehcache-core
- log4j
- slf4j-api
- slf4j-log4j12
2:在Config类中
public void configPlugin(Plugins me){
me.add(new EhCachePlugin())
}
3:拷贝ehcache.xml文件推荐放到src下或者WEB-INF下(其实放哪里都可以,名字叫这个就行);
4:对Action进行注解
@Before(CacheInterceptor.class)//jfinal框架带的
// @CacheName("userPage")自己定义使用缓存的目录,如果没有这个注解的话,默认actionKey为缓存Action
public void userPage(){
System.out.println("这个是主体方法");
String sql = "from user";
int pageNum = getParaToInt("pageNo",1);
setAttr("pageNum", pageNum);
setAttr("userList", User.dao.paginate(pageNum, 2, "select *", sql));
renderFreeMarker("helloworld.html");
}
5:以上代码运行还会报错,还需要在ehcache中配置,如下:配置cachename与java文件中的cachename或者actionKey一致,注意,包括路径映射路径,假如我使用了如下/hello/test,那么我的cacheName就应该为/hello/test/userPage
;
config中的映射
@Override
public void configRoute(Routes me) {
//add表示添加了一个控制器,路径为/hello,HelloControl为处理该请求路径的类。
//加入要访问该控制器下的其他请求,那么在输入http输入/hello/test,就访问到该控制器中的test方法
me.add("/",HelloControl.class);
// me.add("/hello/test",HelloControl.class);
}
XML配置,主要是
部分,顺便添加个配置说明
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="false" monitoring="autodetect"
dynamicConfig="true">
磁盘缓存位置
<diskStore path="java.io.tmpdir"/>
默认缓存配置
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="20"
timeToLiveSeconds="60">
defaultCache>
<cache name="/userPage"
堆内存中最大缓存对象书,0没有限制
maxEntriesLocalHeap="10000"
磁盘中的最大对象书,默认为0不限制
maxEntriesLocalDisk="1000"
是否永久有效,如果为true,timeouts被忽略,永不过期
eternal="false"
当调用flush()是否清除缓存,默认是
overflowToDisk="true"
磁盘缓存的缓存区大小,每个Cache都有自己的一个缓冲区
diskSpoolBufferSizeMB="20"
失效前的空闲秒数,当eternal为false时,这个属性才有效
timeToIdleSeconds="300"
失效前的存活秒数,创建时间到失效时间的时间间隔为存活时间,
timeToLiveSeconds="600"
内存回收策略:使用频率最低:LFU,Less Frequently Used,最近最少使用LRU,Least Recently Used,先进先出,FIFO,Firsh in First Out
memoryStoreEvictionPolicy="LFU"
transactionalMode="off"
是否缓存虚拟机重启期数据
diskPersistent="true"
磁盘失效线程运行时间间隔,默认为120秒
diskExpiryThreadIntervalSeconds="1"
/>
ehcache>
用来修改了数据库信息后实时更新信息;用法如下:
在使用了缓存的查询方法上
@Before(CacheInterceptor.class)
@CacheName("/userPage")
public void userPage(){
System.out.println("这个是主体方法");
String sql = "from user";
int pageNum = getParaToInt("pageNo",1);
setAttr("pageNum", pageNum);
setAttr("userList", User.dao.paginate(pageNum, 5, "select *", sql));
renderFreeMarker("helloworld.html");
}
假如现在添加了一个用户,不清楚缓存则我们看到的数据不能显示新添加的这个用户,那么需要如下操作
@Before({LoginValidator.class,EvictInterceptor.class})
@CacheName("/userPage")
public void addUser() {
User user = getModel(User.class);
boolean flag = user.save();
if (flag) {
redirect("/userPage/");
} else {
renderText("Sorry,有异常,插入失败");
}
}
Validator实际上就是一个Inteceptor,配置地方差不多,在使用的时候,建立一个类,然后继承Validator,里面有两个方法,一个是校验方法,一个是出错后的处理方法
public class LoginValidator extends Validator {
/**
* 用于参数校验的方法
*/
@Override
protected void validate(Controller c) {
/*
field:需要校验的字段,例如页面输入框的name是user.name,那么这里就是user.name
errorKey:出错了的时候返回错误信息,在页面取错误信息所用的键
errorMessage:错误的具体信息
*/
//validateRequiredString("field", "errorKey", errorMessage);
validateRequiredString("name", "nameMsg", "请输入用户名");
validateRequiredString("pass", "passMsg", "请输入密码");
}
/**
* 用于参数校验未通过产生错误的时候处理错误的方法
*/
@Override
protected void handleError(Controller c) {
//取得actionKey之后用以判断是添加出错还是更新出错
String actionKey = getActionKey();
String view = null;
c.keepModel(User.class);//保存上一次提交的表单记录
//失败后返回
System.out.println("actionkey:" + actionKey);
if("/addUser".equals(actionKey)){
System.out.println("这个是添加");
view = "/addUser.html";
}
if("/doUpdateUser".equals(actionKey)){
System.out.println("这个是更新");
view = "/updateUser.html";
}
c.renderFreeMarker(view);
}
}
在控制器中,主要就是添加了一个Validator注解:
@Before({LoginValidator.class,EvictInterceptor.class})
@CacheName("/userPage")
public void addUser() {
User user = getModel(User.class);
boolean flag = user.save();
if (flag) {
redirect("/userPage/");
} else {
renderText("Sorry,有异常,插入失败");
}
}
在页面中,主要就是需要来接收服务器传递过来的错误信息
用户名<input type="text" name="user.name" value="${(user.name)!''}">
${(nameMsg)!''}
<br>
电话号码<input type="text" name="user.phone" value="${(user.phone)!''}">
${(phoneMsg)!''}
<br>
地址<input type="text" name="user.address" value="${(user.address)!''}"><br>
<input type="hidden" name="user.id" value="${(user.id)!''}"><br>
<input type="submit" value="保存">
tomcat重新更新,启动的时候会重新部署项目,那么上传的文件可能丢失
文件上传需要一个jar包支持,当然在jfinal里面集成了
- cos-26Dec2008.jar
文件上传的思路其实就是表单提交的时候添加enctype=”multipart/form-data”属性,method为POST;控制器方面使用getFile(“fileName”)来提取上传的文件,然后jfinal默认文件上传到WebRoot(WebContent根目录下)的upload目录中,当然可以修改。在config中;
代码如下:
package demo;
import java.io.File;
import java.util.UUID;
import com.jfinal.core.Controller;
import com.jfinal.upload.UploadFile;
public class UploadController extends Controller {
/**
* 去向文件上传页面
*/
public void index(){
render("/upload.html");
}
/**
* 执行文件上传操作,加了个try catch是为了 方便调试,不然出错都不知道出错在哪里,实际使用是不加,注意,如果是tomcat服务器启动的项目,文件会上传到eclipse中tomcat的部署目录去eclipse中是看不到的,但是服务器能访问到,如果是jetty的话,就能直接在eclipse中看到
* 或者来个全局Inteceptor
*/
public void doUpload(){
try{
//如何获取带有文件上传的表单中的非文件元素数据
//String title = getPara("title");
// 这个方法不可行了,那么必须要先得到getFile,才能得到相关数据,因为multipart请求要求先解析
UploadFile file = getFile("filename");
//取得文件扩展名
String ext = file.getFileName().substring(file.getFileName().lastIndexOf("."));
//文件重命名
file.getFile().renameTo(new File(file.getSaveDirectory()+UUID.randomUUID()+ext));
// System.out.println(file.getSaveDirectory());
String title = getPara("title");
System.out.println(title);
renderText("success");
}
catch(Exception e){
e.printStackTrace();
}
catch(Error s){
s.printStackTrace();
}
}
}
@Override
public void configConstant(Constants me) {
me.setDevMode(true);
//设置运行在开发模式下的默认视图类型为JSP
me.setViewType(ViewType.FREE_MARKER);
me.setMaxPostSize(10*1024*1024); //单位是字节,1*1024代表1KB,*1024代表1m
//设置文件上传路径
me.setUploadedFileSaveDirectory("jfinalDemoUpload");//可以写绝对路径:当然\分隔符是\\
}