不灭的焱

革命尚未成功,同志仍须努力

作者:php-note.com  发布于:2018-12-14 00:57  分类:Java库/系统 

JFinal

注:我这里只是记了下实际应用,很多细节上的东西没写进来,想学习JFinal的话,可以一边参考JFinal官方文档一边参考我这个,当然,水平有限,只做了一些简单的入门级代码

JFinal基本配置

内置Jetty启动项目:

Jfinal推荐使用WebRoot\WEB-INF\classes放class文件,
于是创建项目的时候Default output folder位置改成上面,然后Content directory要与此对应。,当然也可以用tomcat来使用,跟SSH的新建dynamic Web Project一样新建出来就可;

添加JFinal的Controller过滤器

<filter>
    <filter-name>jfinal</filter-name>
    <filter-class>com.jfinal.core.JFinalFilter</filter-class>
    <init-param>
        <param-name>configClass</param-name>
        <param-value>demo.DemoConfig</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>jfinal</filter-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的配置
     * <p>
     * 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) {
    }
}

HelloControl控制器

package demo;

import com.jfinal.core.ActionKey;
import com.jfinal.core.Controller;

public class HelloControl extends Controller {

    public void index() {
        String msg = "Welcome To JFinal World";
        //将信息传递到页面,页面中读取代码为:
        //<!--为什么有!'',意思是如果读取到的参数为null,使用''代替,
        //因为Freemarker需要安全输出,页面中如果有Null会报错-->
        //<h1>${(helloworld)!''}</h1>
        setAttr("helloworld", msg);
        //使用FreeMarker渲染页面
        renderFreeMarker("helloworld.html");
    }

    //该注解是直接映射,意思测试的时候使用localhost/test就会访问到该路径,当然,服务器端口使用的80
    @ActionKey("test")
    public void test() {
        renderFreeMarker("/helloworld.html");
    }

}

有个小技巧,在html页面中,可以使用
<#include “xxx.html”>
可以将xxx.html里面的东西包含到你需要的页面,例如修改和删除都使用同一个页面的情况下,就可以用的到。

Jfinal MVC及参数的接收,以及数据源配置

数据源配置:

数据源配置即配置数据库连接,在这里使用的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<User(M)>类,是个泛型类
    arp.addMapping("user", User.class);

}

关于Jfinal中Model的配置;

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");
}

JFinal之CRUD

Model书写:

对象的属性是从数据库直接获取的,假如数据库列名是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.list as user> -->
<#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>

控制器书写:

/**
 * 查询所有
 */
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");
}

增加

页面书写:

<form action="addUser">
    <input type="text" name="user.name">
    <input type="text" name="user.phone">

    <input type="submit" value="提交">
</form>

控制器书写:

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

 <form action="updateUser">
    用户名<input type="text" name="user.name"><br>
    电话号码<input type="text" name="user.phone"><br>
    地址<input type="text" name="user.address"><br>
    id<input type="hidden" name="user.id"><br>

    <input type="submit" value="提交">
</form>

控制器书写

/**
 * 修改用户
 */
public void updateUser() {
    User user = getModel(User.class);

    boolean flag = user.update();
    if (flag) {
        redirect("/user/");
    } else {
        renderText("Sorry,有异常,修改失败");
    }

}

Interceptor拦截器

拦截器: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");

思考Jfinal中Service层及AOP实现

研究了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<xxxx>

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集合

import com.jfinal.plugin.activerecord.Model;

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<Blog> getBlogs() {
        return Blog.dao.find("select * from blog where user_id=?", get("id"));
    }
}

ActiveRecord

这个部分包含了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<User> users = User.dao.find("select * from user where age>18");

// 分页查询年龄大于18的user,当前页号为1,每页10个user
Page<User> 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<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);

关于Record的事务

说到事务,其实底层就是把
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);
}

Cache缓存

缓存就是什么,就是你经常要查的东西来放到缓存里,因为开启数据库是个代价比较高昂的操作,所以应用缓存来减少数据库开和关的次数来提升性能,类似连接池,空间换时间;
一般缓存里放的东西都是经常查但又不经常修改的数据,因为缓存可以设置缓存内容失效时间,可以相对也比较灵活。

在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配置,主要是<cache name>部分,顺便添加个配置说明

<?xml version="1.0" encoding="UTF-8"?>
<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>

EvictInterceptor

用来修改了数据库信息后实时更新信息;用法如下:
在使用了缓存的查询方法上

@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,验证器

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中;
代码如下:

页面

<form action="upload/doUpload" method="POST" enctype="multipart/form-data" >
    <input type="text" name="title"><br><br>
    <input type="file" name="filename"><br>
    <input type="submit" value="上传">
</form>

控制器

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");//可以写绝对路径:当然\分隔符是\\
}

 

 

 

摘自:https://blog.csdn.net/dartagnan_wang/article/details/48941335/