为ExtJS的grid panel提供restful服务
ExtJS的grid panel集成了负责前端交互的分页、检索、排序。
用浏览器开发者工具查看,可以看到它提交给后台的restful形式:
page:1 start:0 limit:27 sort:[{"property":"id","direction":"DESC"}] filter:[{"operator":"like","value":"111","property":"title"}]
而它期待获得的返回结果格式如下:
{ "data" : [ { "id" : 1, "dtCreate" : null, "href" : null, "author" : null, "btop" : 0, "content" : "内容第1行内容第2行", "pid" : 0, "plain" : null, "postto" : "政策法规;用户中心#办理流程", "stitle" : "rrrr", "stype" : null, "tag" : null, "title" : "题名11111", "uid" : 0, "status" : 2, "primaryKey" : 1 } ], "total" : 1 }
其中total是分页的依据。
我们以下图所示的思路为grid panel提供restful数据服务:
请求传到后台,先由controller对参数进行一次拆分,page、start、limit这样的简单参数直接就拿到了。
@RequestMapping(value = "/list/{entity}", method = RequestMethod.GET) @ResponseBody public ResultDTO getEntities(@PathVariable String entity, @RequestParam(value = "page", required = false) Integer page, @RequestParam(value = "start", required = false) Integer start, @RequestParam(value = "limit", required = false) Integer limit, @RequestParam(value = "filter", required = false) String filter, @RequestParam(value = "sort", required = false) String sort) throws Exception { return restService.getEntities(page, start, limit, filter, sort, entity); }
然后对filter和sort进行进一步分析,这两个参数是json格式的复杂请求,分析的策略是:在java中定义与前台对应的请求对象,
通过com.fasterxml.jackson.databind.ObjectMapper 直接读入json内容,获得java请求对象。
package net.bat.filter; public class ExtJSFilter { private String operator; private Object value; private String property; private Boolean exactMatch; public Boolean getExactMatch() { return exactMatch; } public void setExactMatch(Boolean exactMatch) { this.exactMatch = exactMatch; } /** * @return the operator */ public String getOperator() { return this.operator; } /** * @param operator the operator to set */ public void setOperator(String operator) { this.operator = operator; } /** * @return the value */ public Object getValue() { return this.value; } /** * @param value the value to set */ public void setValue(Object value) { this.value = value; } /** * @return the property */ public String getProperty() { return property; } /** * @param property the property to set */ public void setProperty(String property) { this.property = property; } }
package net.bat.filter; public class ExtJSSort { private String property; private String direction; /** * @return the property */ public String getProperty() { return property; } /** * @param property the property to set */ public void setProperty(String property) { this.property = property; } /** * @return the direction */ public String getDirection() { return direction; } /** * @param direction the direction to set */ public void setDirection(String direction) { this.direction = direction; } }
ExtJSFilter[] fliters = mapper.readValue(filter, ExtJSFilter[].class); ... ExtJSSort[] sorts = mapper.readValue(sort, ExtJSSort[].class);
接下来,需要将请求转化为JPA检索能接受的请求形式:
publicJPAReq parse(String filter, String sort, Class cls) throws Exception { JPAReq req = new JPAReq(); if (filter != null) { StringBuffer sbuf = new StringBuffer(); int pos = 0; ExtJSFilter[] fliters = mapper.readValue(filter, ExtJSFilter[].class); Object[] queryParams = new Object[fliters.length]; for (ExtJSFilter ef : fliters) { String filedName = ef.getProperty(); // 处理外键 many-to-many if ((ef.getExactMatch() != null) && ef.getExactMatch()) { // TODO add ename filter if (cls == Attach.class) { filedName = "eid"; ef.setOperator("eq"); } else { filedName = "pid"; ef.setOperator("eq"); } } if (pos == 0) { sbuf.append(" " + filedName); } else { sbuf.append(" and " + filedName); } Oper oper = Oper.valueOf(ef.getOperator().toUpperCase()); Object val = ef.getValue(); // EQ, LIKE, GT, LT, GTE, LTE String paraPos = "(?" + (pos + 1) + ")"; switch (oper) { case EQ: sbuf.append(" =" + paraPos); break; case LIKE: sbuf.append(" like " + paraPos); val = "%" + val + "%"; break; case GT: sbuf.append(" >" + paraPos); break; case LT: sbuf.append(" <" + paraPos); break; case GE: sbuf.append(" >=" + paraPos); break; case LE: sbuf.append(" <=" + paraPos); break; case IN: sbuf.append(" in " + paraPos); queryParams[pos++] = val; continue; } // 如果参数值与目标属性不匹配,需要特殊处理,例如:日期 Field f = null; try { f = cls.getDeclaredField(filedName); } catch (Exception e) { // getDeclaredField 无法获得 extend的父类field f = cls.getSuperclass().getDeclaredField(filedName); } Class> fcl = f.getType(); if ((val != null) && !fcl.isAssignableFrom(val.getClass())) { if (fcl.isAssignableFrom(Date.class)) { // val = fmt_date.parse(val.toString()); // dateformat: time val = new Date(Long.parseLong(val.toString())); } else if (fcl.isAssignableFrom(Long.class)) { // 检索传值integer与期待值long不匹配 val = Long.parseLong(val.toString()); } } queryParams[pos++] = val; } req.setWhereql(sbuf.toString()); req.setQueryParams(queryParams); } if (sort != null) { LinkedHashMap orderby = new LinkedHashMap (); ExtJSSort[] sorts = mapper.readValue(sort, ExtJSSort[].class); for (ExtJSSort es : sorts) { orderby.put(es.getProperty(), es.getDirection()); } req.setOrderby(orderby); } return req; } }
调用JPA分页检索,返回结果:
privateQueryResult getEntity(Integer page, Integer start, Integer limit, String filter, String sort, String entity_name) throws Exception { Class cls = UserDAO.classForName(entity_name); JPAReq req = parser.parse(filter, sort, cls); if (start == null) { start = 0; } if (limit == null) { limit = -1; } QueryResult result = dao.getScrollData(cls, start, limit, req.getWhereql(), req.getQueryParams(), req.getOrderby()); return result; }
通用的dao实现借用了网友的工作。