为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行<div>内容第2行</div>", "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检索能接受的请求形式:
public <T> JPAReq parse(String filter, String sort, Class<T> 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<String, String> orderby = new LinkedHashMap<String, String>();
ExtJSSort[] sorts = mapper.readValue(sort, ExtJSSort[].class);
for (ExtJSSort es : sorts) {
orderby.put(es.getProperty(), es.getDirection());
}
req.setOrderby(orderby);
}
return req;
}
}
调用JPA分页检索,返回结果:
private <T extends IdEntity> QueryResult<T> getEntity(Integer page, Integer start, Integer limit, String filter,
String sort, String entity_name) throws Exception {
Class<T> cls = UserDAO.classForName(entity_name);
JPAReq req = parser.parse(filter, sort, cls);
if (start == null) {
start = 0;
}
if (limit == null) {
limit = -1;
}
QueryResult<T> result = dao.getScrollData(cls, start, limit, req.getWhereql(), req.getQueryParams(),
req.getOrderby());
return result;
}
通用的dao实现借用了网友的工作。