1 原例概述
别名重复问题之后,我们还需要解决的问题就是:
如何清除hibernate的上次查询条件,如果不清除,将会导致上次的查询条件和下次的查询条件合并到了一起。
上次的查询条件和本次的查询条件合并到了一起。
解决之前的代码如下:
public String pageQuery() throws Exception { DetachedCriteria dc = pageBean.getDetachedCriteria(); //每一次分页查询的时候应该先清除之前的条件 // 动态添加过滤条件 String addresskey = model.getAddresskey(); if (StringUtils.isNotBlank(addresskey)) { // 添加过滤条件,根据地址关键字模糊查询 dc.add(Restrictions.like("addresskey", "%" + addresskey + "%")); } Region region = model.getRegion(); if (region != null) { String province = region.getProvince(); String city = region.getCity(); String district = region.getDistrict(); //创建别名之前需要判断别名是否存在。 boolean existAlias = existAlias(dc,null,"r"); if(!existAlias){//不存在就创建 dc.createAlias("region", "r"); } if (StringUtils.isNotBlank(province)) { // 添加过滤条件,根据省份模糊查询-----多表关联查询,使用别名方式实现 // 参数一:分区对象中关联的区域对象属性名称 // 参数二:别名,可以任意 dc.add(Restrictions.like("r.province", "%" + province + "%")); } if (StringUtils.isNotBlank(city)) { // 添加过滤条件,根据市模糊查询-----多表关联查询,使用别名方式实现 // 参数一:分区对象中关联的区域对象属性名称 // 参数二:别名,可以任意 dc.add(Restrictions.like("r.city", "%" + city + "%")); } if (StringUtils.isNotBlank(district)) { // 添加过滤条件,根据区模糊查询-----多表关联查询,使用别名方式实现 // 参数一:分区对象中关联的区域对象属性名称 // 参数二:别名,可以任意 dc.add(Restrictions.like("r.district", "%" + district + "%")); } } subareaService.pageQuery(pageBean); this.java2Json(pageBean, new String[] { "currentPage", "detachedCriteria", "pageSize", "decidedzone", "subareas" }); return NONE; }
第二次执行上面的代码将不会得到结果:原因是前一次的查询条件和本次的查询条件合并了。
这个我们可以通过发送的sql语句看出来
解决上述问题原理就是:本次查询之前需要清除hibernate之前的离线查询条件。
下面我们来看看上述问题的出现原因和解决办法。
2 背景
通常我们在使用离线查询技术时, 会这么使用.
如查询BaseDict
对象对应的表中dictTypeCode=006
的记录.
// 创建离线查询对象 DetachedCriteria dc = DetachedCriteria.forClass(BaseDict.class); // 设置查询条件 dc.add(Restrictions.eq("dictTypeCode", "006")); // 利用hibernateTemplate模板根据离线对象查询数据 Listlist = (List ) getHibernateTemplate().findByCriteria(dc);
然而, 当我们需要再次查询BaseDict
中dictTypeCode=009
的记录时, 需要重新创建一个新的DetachedCriteria. 否则, 会将上次dictTypeCode=006
的条件合并起来. 看下图:
3 源码分析
DetachedCriteria dc = DetachedCriteria.forClass(BaseDict.class);
==> 调用构造DetachedCriteria dc = DetachedCriteria.forClass(clazz.getName); // 通过类名构建对象
==>CriteriaImpl(entityName, ...) // 创建Criteria的实现类
注意: 这是实现类会在离线查询对象dc
名为'impl
'属性中持有.
进入CriteriaImpl
会发现, 原来我们add
的所有查询条件会保存在一个叫做:'CriteriaEntries'的ArrayList
中, 并且提供了对应公有方法, 返回该list的Iterator
迭代器.
经过上述分析, 笔者就有思路了:
-
- 利用公有方法获取
CriteriaEntries
的迭代器, 通过遍历删除迭代器中每一个元素, 即实现了清空条件的目的. - 直接简单粗暴, 再次反射, 将
dc
名为'impl
'属性重置, 即new一个新的ArrayList
赋给它.
- 利用公有方法获取
4 解决上述问题的代码实现
思路一: 获取迭代器, 遍历删除
private void eraseCriteria(DetachedCriteria dc) { try { Field impl = dc.getClass().getDeclaredField("impl"); impl.setAccessible(true); // 得到实现类 CriteriaImpl cimpl = (CriteriaImpl) impl.get(dc); // 思路一: 遍历criterionEntries, 清空所有 // 利用实现类的公有方法获取迭代器 IteratorcriterionEntryIterator = cimpl.iterateExpressionEntries(); while (criterionEntryIterator.hasNext()) { // 删除本元素 criterionEntryIterator.remove(); } } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } Log.end(); }
思路二: 直接重置CriteriaEntries
private void eraseCriteria(DetachedCriteria dc) { try { Field impl = dc.getClass().getDeclaredField("impl"); impl.setAccessible(true); // 得到实现类 CriteriaImpl cimpl = (CriteriaImpl) impl.get(dc); // 思路二: 再次反射, 直接将criterionEntries置空. // 获取criterionEntries属性 Field criterionEntries = cimpl.getClass().getDeclaredField("criterionEntries"); criterionEntries.setAccessible(true); // 重置条件list criterionEntries.set(cimpl, new ArrayList()); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } Log.end(); }