该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2008-08-28
关于此问题的起源请阅读我以前的一个帖子:http://www.iteye.com/post/523791。在该帖子中已经实现了从HQL到QBC的转变,在这里就不再重复了。 在上一个帖子中没有模型类Product及Category的代码,为了方便讨论补充如下: public class Category { private Long id; private String name; //类别名称 //Other code omitted } public class Product { private Long id; private String name; //商品名称 private Category category; //商品类别 private Date expDate; //有效期 private Float price; //单价 //Other code omitted } 从前一个帖子中可以看到,使用QBC后代码有所减少,但还是得把构造查询条件的代码写死,这非常不爽。重读了《Java Persistence with Hibernate》一书,发觉QBE是个好东东,于是尝试用改造代码如下: public List<Product> getProducts(Product product) { final Example exampleProduct = Example.create(product). enableLike(MatchMode.ANYWHERE). excludeZeroes(); return (List<Product>) getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { Criteria crit = session.createCriteria(Product.class). add(exampleProduct); return crit.list(); } } ); } 代码非常简洁啊!我只要new一个Product实例,然后把要查询的条件值赋值到相应到属性上,如果某项条件未指定则相应的属性保留为默认的空值,将该实例传递给上面的getProducts方法,就能得到需要的结果了。超爽! 但是我却没办法把这段代码用在产品中,这是因为QBE有着严重的局限性: 1.不能查询指定在关联对象的属性上的条件。比如我想仅列出商品类别名称包括xyz的商品,代码如下: Category category = new Category(); category.setName("xyz"); Product product = new Product(); product.setCategory(category); List<Product> products = getProducts(product); 运行这段代码会列出所有的商品。 2.除了字符串条件可以调用enableLike()方法改用模糊查询外,其它数据类型的条件都只能等值比较。比如我无法查询所有有效的商品(有效期≥当前日期)。 难道就没有办法了吗?经过一番搜索,终于在Hibernate的官网论坛上找到一篇文章:http://forum.hibernate.org/viewtopic.php?t=942872。在该文章中,Dencel写了一个AssociationExample,经过大家的完善,终于解决了查询指定在关联对象的属性上的条件的问题。其主要的奥妙在于: //Hibernate的原版Example //如果属性类型是关联的实体,则忽略 private boolean isPropertyIncluded(Object value, String name, Type type) { return !excludedProperties.contains(name) && !type.isAssociationType() && selector.include(value, name, type); } //改版的AssociationExample private boolean includeAssociations = true; public boolean isIncludeAssociations() { return includeAssociations; } public void setIncludeAssociations(boolean includeAssociations) { this.includeAssociations = includeAssociations; } //如果属性类型是关联的实体,且该关联是一对一或多对一,且includeAssociations为true,则包括该属性 private boolean isPropertyIncluded(Object value, String name, Type type) { return !excludedProperties.contains(name) && selector.include(value, name, type) && (!type.isAssociationType() || (type.isAssociationType() && includeAssociations && !type.isCollectionType())); } 解决了前面提到的第一个问题,第二个问题又怎么办呢?我想到一个办法:如果某个条件要使用其它的比较方式(比如大于等于),提供一个方法让用户为该属性指定比较方法,对于其它属性仍采用缺省的查询/比较方法: //Hibernate原版的Example protected void appendPropertyCondition( String propertyName, Object propertyValue, Criteria criteria, CriteriaQuery cq, StringBuffer buf) throws HibernateException { Criterion crit; if ( propertyValue!=null ) { //当属性值不为空时,如果是字符串且指定为模糊查询,则使用模糊查询,否则使用等值比较 boolean isString = propertyValue instanceof String; SimpleExpression se = ( isLikeEnabled && isString ) ? Restrictions.like(propertyName, propertyValue) : Restrictions.eq(propertyName, propertyValue); crit = ( isIgnoreCaseEnabled && isString ) ? se.ignoreCase() : se; } else { crit = Restrictions.isNull(propertyName); } String critCondition = crit.toSqlString(criteria, cq); if ( buf.length()>1 && critCondition.trim().length()>0 ) buf.append(" and "); buf.append(critCondition); } //增强后的EnhancedExample private static final RestrictionHolder holder = new DefaultRestrictionHolder(); /** * Restriction strategy definitions */ public static enum RestrictionStrategy {eq, ne, gt, lt, ge, le} /** * Restriction strategy holder for the query criteria */ public static interface RestrictionHolder { /** * Set a restriction strategy for a POJO's property */ public RestrictionHolder set(String propertyName, RestrictionStrategy strategy); /** * Get the restriction strategy of the property */ public RestrictionStrategy get(String propertyName); } static final class DefaultRestrictionHolder implements RestrictionHolder { private Map<String, RestrictionStrategy> strategies = new HashMap<String, RestrictionStrategy>(); public RestrictionHolder set(String propertyName, RestrictionStrategy strategy) { strategies.put(propertyName, strategy); return this; } public RestrictionStrategy get(String propertyName) { return strategies.get(propertyName); } } /** * Get the restriction strategy holder */ public RestrictionHolder getRestrictionHolder() { return holder; } protected void appendPropertyCondition( String propertyName, Object propertyValue, Criteria criteria, CriteriaQuery cq, StringBuffer buf) throws HibernateException { Criterion crit; if ( propertyValue!=null ) { //当属性值不为空时,如果为该属性指定了比较条件,则使用指定的比较条件 RestrictionStrategy strategy = holder.get(propertyName); if ( strategy != null ) { switch(strategy) { //case eq: crit = Restrictions.eq(propertyName, propertyValue); case ne: crit = Restrictions.ne(propertyName, propertyValue); break; case gt: crit = Restrictions.gt(propertyName, propertyValue); break; case lt: crit = Restrictions.lt(propertyName, propertyValue); break; case ge: crit = Restrictions.ge(propertyName, propertyValue); break; case le: crit = Restrictions.le(propertyName, propertyValue); break; default: crit = Restrictions.eq(propertyName, propertyValue); }; } else { //否则使用默认的比较条件:如果是字符串且指定为模糊查询,则使用模糊查询,否则使用等值比较 boolean isString = propertyValue instanceof String; SimpleExpression se = ( isLikeEnabled && isString ) ? Restrictions.like(propertyName, propertyValue) : Restrictions.eq(propertyName, propertyValue); crit = ( isIgnoreCaseEnabled && isString ) ? se.ignoreCase() : se; } } else { crit = Restrictions.isNull(propertyName); } String critCondition = crit.toSqlString(criteria, cq); if ( buf.length()>1 && critCondition.trim().length()>0 ) buf.append(" and "); buf.append(critCondition); } 于是前面getProducts方法只需要简单修改一下: public List<Product> getProducts(Product product) { //改用EnhancedExample来允许关联对象的条件查询 final EnhancedExample exampleProduct = EnhancedExample.create(product). enableLike(MatchMode.ANYWHERE). excludeZeroes(); //指定expDate属性使用大于等于比较方法 exampleProduct.getRestrictionHolder(). set("expDate", EnhancedExample.RestrictionStrategy.ge); return (List<Product>) getHibernateTemplate().execute( new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { Criteria crit = session.createCriteria(Product.class). add(exampleProduct); return crit.list(); } } ); } 经过以上改进,QBE的实用性大大提高,能够真正解决较大多数的组合查询问题。 已知的问题:以上“增强的”QBE还无法解决范围查询(比如价格在0到1000之间),这是因为一个属性只能携带一个值(你不可能指定两个值给Product.price属性)。这种情况下需要修改getProducts方法,增加参数把价格范围传递进来,再以QBC方式把相应的条件加到crit变量上。范例代码就不再给出了。 完整的EnhancedExample源码请见附件。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-08-28
1.4不支持enum
|
|
返回顶楼 | |
发表时间:2008-08-28
ray_linn 写道 浪费这么多行代码,做的都是无用功。
不要打击别人的激情,lz也只是给出了一个他认为最佳的方案,提供出来给大家讨论。实际上这个问题大家都遇到,每回给不同的查询单写一个方法,很费劲。这种通用查询还是很有价值的,如果能实现,能省掉很多的工作量。 |
|
返回顶楼 | |
发表时间:2008-08-28
pn2008 写道 1.4不支持enum
不错,这些代码用到了枚举和泛型,需要JDK 5以上版本才能运行。如果你有兴趣,改成在JDK 1.4上运行也不会有什么困难吧? |
|
返回顶楼 | |
发表时间:2008-08-28
ray_linn 写道 浪费这么多行代码,做的都是无用功。
能否请你说明一下为何你认为这么做是浪费,是做无用功?对于文中提到的问题,你会怎么解决呢? |
|
返回顶楼 | |
发表时间:2008-08-28
pheh 写道 ray_linn 写道 浪费这么多行代码,做的都是无用功。
不要打击别人的激情,lz也只是给出了一个他认为最佳的方案,提供出来给大家讨论。实际上这个问题大家都遇到,每回给不同的查询单写一个方法,很费劲。这种通用查询还是很有价值的,如果能实现,能省掉很多的工作量。 虽然有点受打击,还是可以承受 不能说这是最佳方案,因为它不能解决所有的问题。但我认为它确实比Hibernate原版的Example进步了一些,所以提供出来给大家讨论,希望能够听到各位有建设性的建议,让我能改进它。如果各位有跟我的想法截然不同,但确实能解决我的问题的方案,那也很好啊 |
|
返回顶楼 | |
发表时间:2008-08-28
这帖子挺不错的还投新手帖,对javaeye的大牛真是越来越不明白了
|
|
返回顶楼 | |
发表时间:2008-08-28
这个不错,投个良好贴:D
ibatis可以很好的解决这类问题。 |
|
返回顶楼 | |
发表时间:2008-08-29
cats_tiger 写道 这个不错,投个良好贴:D
ibatis可以很好的解决这类问题。 不懂ibatis,能否请你大致地描述一下解决办法或思路? |
|
返回顶楼 | |
发表时间:2008-08-29
根据用户输入,动态创建查询是很常见的需求,但是Query By Example只能解决简单的属性对比,正如同你说的,between查询或者一个in查询都是很麻烦的。
Query By Criterion是解决这种需求的最好方法,你需要实现的只是一个将用户输入转化成动态创建Criterion的通用方法,能够将map或者list之类容器中的值转化即可: ["eq", "propertyName", "abc"], ["between", "propertyName", "lo", "hi"], ["in", "propertyName", [1, 2, 3]] => Restrictions.eq and Restrictions.between and Restrictions.in Restrictions只是对各种Criterion实现的提供了静态方法调用,你也可以直接实现一个map/list => criterion的转化方法。 |
|
返回顶楼 | |