在开发web应用程序中,表单查询是一个非常常见的功能,但是需求五花八门,各种各样,更重要的是,后端的SQL/hql要根据页面的输入动态变化。常常形成一个庞大的拼sql的方法,并且各个方法都不一样,为开发,维护带来了不少困难。本人在总结了常用需求的基础上,再结合SSH,想出一个比较通用的方案,以此抛砖引玉。
实现的思路是这样的,根据页面输入,自动将页面输入转化成一组查询条件,数据访问层再根据这些查询条件自动组装SQL/hql。这个是查询条件类。
import java.io.Serializable;
/**
* 表单上的查询条件封装类
* 封装表单的查询条件
*
* @auther: XXX
* Date: 11-4-22
* Time: 下午2:16
* @version: 1.0
*/
public class QueryCondition implements Serializable {
/**
* 查询主实体名称
*/
private String enityName;
/**
* 实体对应的属性名称
*/
private String propertyName;
/**
* 实体对应的属性类型
*/
private String propertyType;
/**
* 查询关系符号
*/
private Operator operator;
/**
* like查询时候的匹配模式
*/
private MatchMode matchMode;
/**
* 条件的值
*/
private String value;
/**
* 输入的第二个值
*/
private String value2;
/**
* 多个值
*/
private String[] multipleValue;
public MatchMode getMatchMode() {
return matchMode;
}
public void setMatchMode(MatchMode matchMode) {
this.matchMode = matchMode;
}
public String getEnityName() {
return enityName;
}
public void setEnityName(String enityName) {
this.enityName = enityName;
}
public String getPropertyName() {
return propertyName;
}
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
public String getPropertyType() {
return propertyType;
}
public void setPropertyType(String propertyType) {
this.propertyType = propertyType;
}
public Operator getOperator() {
return operator;
}
public void setOperator(Operator operator) {
this.operator = operator;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getValue2() {
return value2;
}
public void setValue2(String value2) {
this.value2 = value2;
}
public String[] getMultipleValue() {
return multipleValue;
}
public void setMultipleValue(String[] multipleValue) {
this.multipleValue = multipleValue;
}
}
这个是oprator类,包含了常用的操作符
/**
* 关系操作符号
*
* @author XX
* @version 1.0
*/
public enum Operator {
LIKE, // Like
LT, // 小于
GT, // 大于
LE, // 小于等于
GE, // 大于等于
NE, // 不等于
IN, // IN
BETWEEN, //between
EQ //等于
}
MathcMode 匹配模式类,模仿hibernate
/**
* @auther: XXX
* Date: 11-4-27
* Time: 下午4:40
* @version: 1.0
*/
public enum MatchMode {
//精确匹配
EXACT,
BEFORE,//前匹配
AFTER, //后匹配
ANYWHERE //任意匹配
}
根据查询条件构造criteria
/**
* 根据查询条件组件 QueryBuilder
* QueryBuilder 的用法请参考hibernate源代码
*
* @param conditions 查询条件
* @return
*/
protected Criteria buildCritria(final List<QueryCondition> conditions) {
if (conditions != null && conditions.size() > 0) {
Criteria criteria = getSession().createCriteria(entityClass);
ClassMetadata metadata = getSessionFactory().getClassMetadata(entityClass);
for (QueryCondition qc : conditions) {
if (qc == null) continue;
Type type = metadata.getPropertyType(qc.getPropertyName());
switch (qc.getOperator()) {
case LIKE:
if (StringUtils.isNotEmpty(qc.getValue())) {
switch (qc.getMatchMode()) {
case EXACT:
criteria.add(Restrictions.like(qc.getPropertyName(), qc.getValue(), MatchMode.EXACT));
break;
case AFTER:
criteria.add(Restrictions.like(qc.getPropertyName(), qc.getValue(), MatchMode.END));
break;
case BEFORE:
criteria.add(Restrictions.like(qc.getPropertyName(), qc.getValue(), MatchMode.START));
break;
case ANYWHERE:
criteria.add(Restrictions.like(qc.getPropertyName(), qc.getValue(), MatchMode.ANYWHERE));
break;
default:
criteria.add(Restrictions.like(qc.getPropertyName(), qc.getValue(), MatchMode.ANYWHERE));
break;
}
}
break;
case LT:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
criteria.add(Restrictions.lt(qc.getPropertyName(), value));
}
break;
case GT:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
criteria.add(Restrictions.gt(qc.getPropertyName(), value));
}
break;
case LE:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
criteria.add(Restrictions.le(qc.getPropertyName(), value));
}
break;
case GE:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
criteria.add(Restrictions.ge(qc.getPropertyName(), value));
}
break;
case NE:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
criteria.add(Restrictions.ne(qc.getPropertyName(), value));
}
break;
case IN:
if (qc.getMultipleValue() != null && qc.getMultipleValue().length > 0) {
Object[] values = new Object[qc.getMultipleValue().length];
for (int i = 0; i < qc.getMultipleValue().length; i++) {
values[i] = stringToObject(qc.getMultipleValue()[i], type);
}
criteria.add(Restrictions.in(qc.getPropertyName(), values));
}
break;
case BETWEEN:
if (StringUtils.isNotEmpty(qc.getValue()) || StringUtils.isNotEmpty(qc.getValue2())) {
criteria.add(Restrictions.between(qc.getPropertyName(), qc.getValue(), qc.getValue2()));
}
break;
default:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
criteria.add(Restrictions.eq(qc.getPropertyName(), value));
}
break;
}
}
return criteria;
}
return null;
}
根据查询条件构造hql
/**
* 根据查询条件组件 QueryBuilder
* QueryBuilder 的用法请参考hibernate源代码
*
* @param conditions 查询条件
* @return
*/
protected QueryBuilder buildQuery(final List<QueryCondition> conditions) {
if (conditions != null && conditions.size() > 0) {
String entityName = entityClass.getSimpleName();
QueryBuilder queryBuilder = new QueryBuilder(entityName, StringUtils.uncapitalize(entityName));
ClassMetadata metadata = getSessionFactory().getClassMetadata(entityClass);
Parameters parameters = queryBuilder.getRootParameters();
for (QueryCondition qc : conditions) {
if (qc == null) continue;
Type type = metadata.getPropertyType(qc.getPropertyName());
switch (qc.getOperator()) {
case LIKE:
if (StringUtils.isNotEmpty(qc.getValue())) {
String value = qc.getValue();
switch (qc.getMatchMode()) {
case EXACT:
break;
case BEFORE:
value = "%" + value;
break;
case AFTER:
value = value + "%";
break;
case ANYWHERE:
value = "%" + value + "%";
break;
default:
value = "%" + value + "%";
break;
}
parameters.addWhereWithParam(qc.getPropertyName(), "like", value);
}
break;
case LT:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
parameters.addWhereWithParam(qc.getPropertyName(), "<", value);
}
break;
case GT:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
parameters.addWhereWithParam(qc.getPropertyName(), ">", value);
}
break;
case LE:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
parameters.addWhereWithParam(qc.getPropertyName(), "<=", value);
}
break;
case GE:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
parameters.addWhereWithParam(qc.getPropertyName(), ">=", value);
}
break;
case NE:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
parameters.addWhereWithParam(qc.getPropertyName(), "!=", value);
}
break;
case IN:
if (qc.getMultipleValue() != null && qc.getMultipleValue().length > 0) {
Object[] values = new Object[qc.getMultipleValue().length];
for (int i = 0; i < qc.getMultipleValue().length; i++) {
values[i] = stringToObject(qc.getMultipleValue()[i], type);
}
parameters.addWhereWithParams(qc.getPropertyName(), "in (", values, ")");
}
break;
case BETWEEN:
if (StringUtils.isNotEmpty(qc.getValue()) && StringUtils.isEmpty(qc.getValue2())) {
Object minValue = stringToObject(qc.getValue(), type);
parameters.addWhereWithParam(qc.getPropertyName(), ">=", minValue);
} else if (StringUtils.isEmpty(qc.getValue()) && StringUtils.isNotEmpty(qc.getValue2())) {
Object maxValue = stringToObject(qc.getValue(), type);
parameters.addWhereWithParam(qc.getPropertyName(), "<=", maxValue);
} else if (StringUtils.isNotEmpty(qc.getValue()) && StringUtils.isNotEmpty(qc.getValue2())) {
Object minValue = stringToObject(qc.getValue(), type);
Object maxValue = stringToObject(qc.getValue2(), type);
parameters.addWhereWithParam(qc.getPropertyName(), ">=", minValue);
parameters.addWhereWithParam(qc.getPropertyName(), "<=", maxValue);
}
break;
default:
if (StringUtils.isNotEmpty(qc.getValue())) {
Object value = stringToObject(qc.getValue(), type);
parameters.addWhereWithParam(qc.getPropertyName(), "=", value);
}
break;
}
}
return queryBuilder;
}
return null;
}
由于从前端传来的参数都是String型的,所以需要把String型的数据进行数据类型转换。这里借助了hibernate的实现。
/**
* 从字符串到java对象的转换
*
* @param value 值
* @param type 类型
* @return
*/
protected Object stringToObject(String value, Type type) {
Object ret = null;
if (type instanceof TimestampType) {
SimpleDateFormat sdf = new SimpleDateFormat();
if (value.length() > 10) {
sdf.applyPattern("yyyy-MM-dd HH:mm:ss");
} else {
sdf.applyPattern("yyyy-MM-dd");
}
try {
ret = sdf.parse(value);
} catch (ParseException e) {
e.printStackTrace();
}
} else if (type instanceof AbstractStandardBasicType) {
ret = ((AbstractStandardBasicType) type).fromString(value);
}
return ret;
}
调用方法执行
/**
* 根据条件自动查询
*
* @param conditions 查询条件
* @return
*/
public final List<T> findByCondition(final List<QueryCondition> conditions) {
Criteria criteria = buildCritria(conditions);
if (criteria != null) {
//增加自定义的其他查询条件
addCustomCriterion(criteria);
return criteria.list();
}
return null;
}
为了能将页面上输入值变成查询条件的List,结合了struts2的自动封装数据的特性,在html中只要这样写,就可以自动将输入项目组装成查询条件的List
<td width="9%" class="right">
用户编号<@splitor/>
</td>
<td width="18%">
<input type="hidden" name="condition[0].propertyName" value="userCode"/>
<select id="__select_userCode" name="condition[0].operator" dojoType="dijit.form.Select">
<option value="LIKE">模糊查询</option>
<option value="EQ">相等</option>
<option value="LT">小于</option>
<option value="GT">大于</option>
<option value="LE">小于等于</option>
<option value="GE">大于等于</option>
<option value="NE">不等于</option>
<option value="IN">等于多个</option>
<option value="BETWEEN">范围</option>
</select>
<input type="hidden" name="condition[0].matchMode" value="BEFORE"/>
<input dojoType="dijit.form.TextBox" name="condition[0].value" id="userCode"/>
</td>
<td width="9%" class="right">
名称<@splitor/>
</td>
<td width="18%">
<input type="hidden" name="condition[1].propertyName" value="userName"/>
<select id="__select_userName" name="condition[1].operator" dojoType="dijit.form.Select">
<option value="LIKE">模糊查询</option>
<option value="EQ">相等</option>
<option value="LT">小于</option>
<option value="GT">大于</option>
<option value="LE">小于等于</option>
<option value="GE">大于等于</option>
<option value="NE">不等于</option>
<option value="IN">等于多个</option>
<option value="BETWEEN">范围</option>
</select>
<input type="hidden" name="condition[1].matchMode" value="ANYWHERE"/>
<input dojoType="dijit.form.TextBox" name="condition[1].value" id="userName"/>
</td>
只要在Action或者Model中定义这样的一个属性,就可以完成自动转换。
/**
* 查询条件
*/
protected List<QueryCondition> condition;
当页面提交或者ajax请求时,会自动将上面的输入变成condition,只要将condition传入上述的方法内,就会自动组装查询条件。
为了让页面书写简单,将上面的输入项目写成一个宏吧,这样使用更方便。
<#-- |查询条件 | property 属性名 必须输入的参数 | index 顺序号 可选参数,但是要保证它不重复 | id 每个输入项的Id,可选参数,但是在使用dojo的情况下,要保证它不重复 | type 输入框的类型,这个类型是指的dojo的类型。 | 大体来说分为,文本,下拉单,日期输入框,数字输入框 ,复选框,单选框 这几种 --> <#macro seniorQuery property index=0 id="id" type="text"> <label for="${id}"><@s.text name="${property}"/>:</label> <input type="hidden" name="condition[${index}].propertyName" value="${property}"/> <select id="__select_${id}" name="condition[${index}].operator" dojoType="dijit.form.Select"> <option value="LIKE">模糊查询</option> <option value="EQ">相等</option> <option value="LT">小于</option> <option value="GT">大于</option> <option value="LE">小于等于</option> <option value="GE">大于等于</option> <option value="NE">不等于</option> <option value="IN">等于多个</option> <option value="BETWEEN">范围</option> </select> <#--根据类型决定输入控件的类型--> <#local controlType = "dijit.form.TextBox"> <#if type=="number"> <#local controlType="dijit.form.NumberTextBox"> <#elseif type="date"> <#local controlType="dijit.form.DateTextBox"> <#elseif type="combo"> <#local controlType="dojo.form.ComboBox"> <#elseif type="checkbox"> <#local controlType="dojo.form.CheckBox"> <#elseif type="radio"> <#local controlType="dojo.form.Radio"> </#if> <input id="${id}" name="condition[${index}].value" dojoType="${controlType}"/> <script type="text/javascript"> dojo.ready(function() { dojo.connect(dijit.byId("__select_${id}"), "onChange", function() { var operator = dijit.byId("__select_${id}").value; if (operator == "BETWEEN") { var newObj = new ${controlType}({ id:"${id}Max", name:"condition[${index}].value2"}); newObj.placeAt(dijit.byId("${id}").domNode, "after"); } else { if (typeof(dijit.byId("${id}Max")) != "undefined") { dijit.byId("${id}Max").destroy(); } } }); }) </script> </#macro>
这样使用起来更方便,你的查询方法会更容易维护,因为所有 表单的查询条件,都被自动创建为criteria了,额外的关联你可以在addCustomCriterion(criteria);里面实现,这个方法是空方法,你只要新建一个子类,重写此方法就可以了。
当然你可以扩展这些宏,或者这些方法。