使用Hibernate Criteria完成灵活的组合查询
在Hibernate中,进行查询主要提供了两个核心API:Query以及Criteria,前者是针对HQL语句的,就是说是面向结构化语句的查询,而后者是面向对象(非常适合没有SQL基础的程序开发人员使用)。
怎样生成Criteria?
生成criteria需要借助Hibernate的Session#createCriteria(Class
select * from `t_user` where `username`='张三';
其中查询原子语句可以认为是username
=’张三’,Criteria的出发也是这样子的,它主要为你提供生成查询原子语句的方法,以及语句之间的逻辑组合,还有就是最常用的排序。我们想要的,基本而简单的,Criteria都提供给我们了。
Criteria提供了#add方法为我们添加Criteria专用表达式Criterion。
现在需要了解下,怎样生成Criterion表达式。Hibernate3之后为我们提供的API是Restrictions实现类,该类为我们提供了静态方法来生成查询原子语句生成方法#eq、#ne、#gt、#lt、#ge、#le、#in、#like等等
方法接受的参数虽然说都不一样,但是也是极其相似的,就是String property(属性的名称) ,返回值都是Criterion。
比如上述的username
=’张三’,可以使用Restrictions生成的Criterion查询表达式是Restrictions.eq(“username”, “张三”):Criterion。这里的“username”是你的领域模型中的属性名称,而不是数据库的列名称。
Criteria中的方法
Criteria#add(Criterion c)以及Criteria#addOrder(Order order)是Criteria中核心的两个方法。一个用于添加Criterion表达式,另外一个用于添加排序。
介绍完了Criteria、Criterion、Restrictions,下面讲讲项目开发中常见的组合查询实现问题。
组合查询作为一个应用程序的基本功能,具有灵活多变的需求,现在的三层开发模式下,可能会在Dao对应的DaoImpl(Dao实现中,一般Dao作为接口,定义实现需要的方法),手工写入具体的hql语句来实现,如果在类不多的前提下(一般来说,一个类对应一个DaoImpl),这样写还算比较简单, 虽说实现简单,却不见的灵活。比方说客户今天说,按照用户名查询用户、明天又改变需求了,需要添加部门来查询用户,如果你是手工写入具体hql的,那么,你就不得不去更改DaoImpl中的hql语句了,如果你设计得比较好的话,或许不需要改变入参(这是最理想的情况了),但是WEB层的参数更改是或许是需要的,这样算算,就改动了持久化层与WEB层。那么怎样不需要代码,就能满足任意的组合查询呢?(仔细想想,这样做,开发量是减少了,如果不约束查询串,那么会有安全性问题)。
在这里我提供一种思路:前台无论传递多少参数,都需要经过HttpServletRequest对象,所有,只要使用这个对象,你就可以取得任意的参数串。而Dao层,要保证Criteria能自由地组合参数(这句话肯定是很抽象的),你可以在Dao中,使用命令行设计模式,结合组合查询的方法combineQuery(Set commands)来进行实现,commands一般就包含了子查询和排序两种命令在里面,在方法内部通过add(Criterion)以及addOrder(Order)来将属性以及排序拼接。看看以下的代码:
命令行接口:
package com.test;
import org.hibernate.Criteria;
/**
* @author daniel
*
*/
public interface CriteriaCommand {
public Criteria execute(Criteria c);
}
域命令行实现类:
/**
*
*/
package com.test;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.hibernate.Criteria;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Restrictions;
/**
* @author daniel
*
*/
public class FieldCommandImpl implements CriteriaCommand {
public static enum OperationMode {
EQ, NE, GT, LT, GE, LE, IN, LIKE
}
//属性名称
private String propertyName;
//属性值
private Object propertyValue;
//操作
private OperationMode operation;
public FieldCommandImpl()
{
this(null, null, OperationMode.EQ);
}
public FieldCommandImpl(String propertyName, Object propertyValue, OperationMode mode)
{
this.propertyName = propertyName;
this.propertyValue = propertyValue;
this.operation = mode;
}
/**
* @return the propertyName
*/
public Object getPropertyName() {
return propertyName;
}
/**
* @param propertyName the propertyName to set
*/
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
/**
* @return the propertyValue
*/
public Object getPropertyValue() {
return propertyValue;
}
/**
* @param propertyValue the propertyValue to set
*/
public void setPropertyValue(Object propertyValue) {
this.propertyValue = propertyValue;
}
/**
* @return the operation
*/
public OperationMode getOperation() {
return operation;
}
/**
* @param operation the operation to set
*/
public void setOperation(OperationMode operation) {
this.operation = operation;
}
@SuppressWarnings("unchecked")
public Criteria execute(Criteria c) {
// TODO Auto-generated method stub
if(c == null)
{
throw new NullPointerException();
}
else
{
if(OperationMode.EQ.equals(operation))//等于
{
c.add(Restrictions.eq(propertyName, propertyValue));
}
else if(OperationMode.NE.equals(operation))//不等于
{
c.add(Restrictions.ne(propertyName, propertyValue));
}
else if(OperationMode.GT.equals(operation))//大于
{
c.add(Restrictions.gt(propertyName, propertyValue));
}
else if(OperationMode.GE.equals(operation))//大于等于
{
c.add(Restrictions.ge(propertyName, propertyValue));
}
else if(OperationMode.LT.equals(operation))//小于
{
c.add(Restrictions.lt(propertyName, propertyValue));
}
else if(OperationMode.LE.equals(operation))//小于等于
{
c.add(Restrictions.le(propertyName, propertyValue));
}
else if(OperationMode.IN.equals(operation))//in操作
{
if(propertyValue instanceof List)
{
List<Object> in_params = (List<Object>) propertyValue;
c.add(Restrictions.in(propertyName, in_params));
}
else if(propertyValue instanceof Set)
{
Set<Object> in_params = (Set<Object>) propertyValue;
c.add(Restrictions.in(propertyName, in_params));
}
else if(propertyValue instanceof Object[])
{
Object[] in_params = (Object[]) propertyValue;
c.add(Restrictions.in(propertyName, in_params));
}
else
{
throw new IllegalArgumentException();//其他,非法参数
}
}
else if(OperationMode.LIKE.equals(operation))
{
c.add(Restrictions.like(propertyName, (String) propertyValue, MatchMode.ANYWHERE));//%a%的匹配模式
}
}
return c;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
// TODO Auto-generated method stub
HashCodeBuilder builder = new HashCodeBuilder();
builder.append(propertyName);
builder.append(propertyValue);
builder.append(operation);
return builder.toHashCode();
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if(obj == null)
{
return false;
}
if(obj == this)
{
return true;
}
if(!obj.getClass().equals(this.getClass()))
{
return false;
}
FieldCommandImpl command = (FieldCommandImpl) obj;
EqualsBuilder builder = new EqualsBuilder();
builder.append(propertyName, command.getPropertyName());
builder.append(propertyValue, command.getPropertyValue());
builder.append(operation, command.getOperation());
return builder.isEquals();
}
}
排序命令行实现类:
/**
*
*/
package com.test;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.hibernate.Criteria;
import org.hibernate.criterion.Order;
/**
* @author daniel
*
*/
public class SortCommandImpl implements CriteriaCommand {
public static enum SortMode {
ASC, DESC
}
private String propertyName;
private SortMode sortMode;
public SortCommandImpl()
{
this(null, SortMode.ASC);//默认增序排序
}
public SortCommandImpl(String propertyName, SortMode sortMode)
{
this.propertyName = propertyName;
this.sortMode = sortMode;
}
/**
* @return the propertyName
*/
public String getPropertyName() {
return propertyName;
}
/**
* @param propertyName the propertyName to set
*/
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
/**
* @return the sortMode
*/
public SortMode getSortMode() {
return sortMode;
}
/**
* @param sortMode the sortMode to set
*/
public void setSortMode(SortMode sortMode) {
this.sortMode = sortMode;
}
public Criteria execute(Criteria c) {
// TODO Auto-generated method stub
if(c == null)
{
throw new NullPointerException();
}
else
{
if(SortMode.ASC.equals(sortMode))//升序
{
c.addOrder(Order.asc(propertyName));
}
else if(SortMode.DESC.equals(sortMode))//降序
{
c.addOrder(Order.desc(propertyName));
}
else
{
throw new IllegalArgumentException();
}
return c;
}
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
// TODO Auto-generated method stub
HashCodeBuilder builder = new HashCodeBuilder();
builder.append(propertyName);
builder.append(sortMode);
return builder.toHashCode();
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if(obj == null)
{
return false;
}
if(obj == this)
{
return true;
}
if(!obj.getClass().equals(this.getClass()))
{
return false;
}
SortCommandImpl command = (SortCommandImpl) obj;
EqualsBuilder builder = new EqualsBuilder();
builder.append(propertyName, command.getPropertyName());
builder.append(sortMode, command.getSortMode());
return builder.isEquals();
}
}
为什么会有命令行接口?其实就是为了方法参数更加优雅。
为什么要区分域命令行以及排序命令行?
其实是针对add(Criterion)以及addOrder(Order order)两个方法的调用。
在生成子查询时,Criterion需要的信息主要是属性名称、属性值以及操作类型,这样才能决定生成什么样的Criterion。比方说,传递进来的FieldCommandImpl中propertyName=username、而propertyValue=张三,mode=OperationMode.EQ,就可以决定生成一个Restrictions.eq(propertyName,propertyValue)的Criterion表达式。
在生成排序时,需要调用生成对应的Order对项,而Order#asc(String propertyName):Order以及Order.desc#desc(String propertyName):Order。毕竟排序时需要的信息比生成原子查询Criterion表达式i信息少,只需要知道属性名称和排序方式就够了。
Dao层的实现方法:
/**
* 适用于组合查询
*/
public List<E> zuheQuery(final Class<?> entityClass, final Set<CriteriaCommand> commands)
{
return template.execute(new HibernateCallback<List<E>>() {
public List<E> doInHibernate(Session arg0)
throws HibernateException, SQLException {
// TODO Auto-generated method stub
Criteria criteria = arg0.createCriteria(entityClass);
//使用两个循环完成 了查询子句和排序子句的拼接
if(commands != null && !commands.isEmpty())
{
for(CriteriaCommand command : commands)
{
command.execute(criteria);
}
}
return criteria.list();
}
});
只要Service层对Web层暴露API,就可以在Web层使用了。上面说过,如果保证Web传递的参数无差异化(也就是说传递过来多少个参数,都没有关系,能够自动将参数解析,并且保证一个不少地传递给Service层),那有一个核心的对象就是request了,可以使用它作为参数,当然,request获取到的参数太原始,需要我们进行处理,所以,可以创建一个QueryFilter类,来封装request并且对request参数进行处理。时间有限,今天就先写到这里。