论坛首页 Java企业应用论坛

介绍DynamicQueryTool,一个用于解决“拼装动态查询”的小工具。

浏览 37256 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-03-10  
想法是一个好想法。
做法上觉得有点问题。
比如:
1.例子中的SQL语法有点怪,如前面的“ <count>select count(*)</count> <list>select o</list>”。
关键是,每一个查询字段名都出现了很多次,我觉得肯定可以减少重复的次数。
数一下:
1)<name>and o.name = :name</name>
2).acceptTag("name", vo.getUserName() != null) 
3)new Object[] { "name",。。。
太多代码了,光是写对"name"和vo.getUserName()之间的关系就足以容纳一定数目的bug了。(按概率算)
BTW,这样的写法看上去很适于代码生成,kidding

还有一个问题,就是默认情况下,我觉得acceptTag的功能应该可以省略,一般只要对应的查询条件值为null既可忽略此查询条件,对于数值为0则忽略的情况,是否能将默认值从0改为null呢,这样可以统一起来。从而将acceptTag这一环节整体省略掉。

良好的定义问题是成功的一半,我试着给出你们正要解决的问题的定义,看是否对你们有帮助:
多条件查询问题:对于一个记录集的查询,界面给出多个查询条件,填写了值的项目有效,忽略未填值的项目,所有查询条件都按"与"操作联合。

0 请登录后投票
   发表时间:2007-03-10  
Lucas Lee 写道
想法是一个好想法。
做法上觉得有点问题。
比如:
1.例子中的SQL语法有点怪,如前面的“ <count>select count(*)</count> <list>select o</list>”。
关键是,每一个查询字段名都出现了很多次,我觉得肯定可以减少重复的次数。
数一下:
1)<name>and o.name = :name</name>
2).acceptTag("name", vo.getUserName() != null) 
3)new Object[] { "name",。。。
太多代码了,光是写对"name"和vo.getUserName()之间的关系就足以容纳一定数目的bug了。(按概率算)
BTW,这样的写法看上去很适于代码生成,kidding

还有一个问题,就是默认情况下,我觉得acceptTag的功能应该可以省略,一般只要对应的查询条件值为null既可忽略此查询条件,对于数值为0则忽略的情况,是否能将默认值从0改为null呢,这样可以统一起来。从而将acceptTag这一环节整体省略掉。

良好的定义问题是成功的一半,我试着给出你们正要解决的问题的定义,看是否对你们有帮助:
多条件查询问题:对于一个记录集的查询,界面给出多个查询条件,填写了值的项目有效,忽略未填值的项目。



信息确实有些重复。不过你提到的1,2,3的信息里面的name并不一定代表同样的意思。 比如第一个name其实和o.name或者那个参数名:name没有什么挂念,用户也可以这样定义 <un>and o.name = :name</un> .

不过,感谢 downpour提出一个很好的意见,就是我们可以参照hibernate提供setProperties,让用户可以不用写那么复杂的setParam的语句。
0 请登录后投票
   发表时间:2007-03-10  
firebody 写道

信息确实有些重复。不过你提到的1,2,3的信息里面的name并不一定代表同样的意思。 比如第一个name其实和o.name或者那个参数名:name没有什么挂念,用户也可以这样定义 <un>and o.name = :name</un> .


的确不是一样的意思,但是那些东西都是连带的,而且的确可以缩减的。
0 请登录后投票
   发表时间:2007-03-10  
俺也类似写了一个,而且加入类ibatis映射功能;大概调用方式如下
构照查询sql:
User user = UserContext.getContextUser(request);
StringBuffer hql = new StringBuffer();
        hql.append("select ")
                .append("mms.id into form.id,")
                //将找到的mms.id值放入bean的id字段里,其余略
                .append("user.id into form.userId,")
                .append("provider.id into form.providerId,")
                .append("mms.name into form.name,")
                .append("mms.url into form.url,")
                .append("mms.filePath into form.filePath,")
                .append("mms.imgPath into form.imgPath,")
                .append("mms.description into form.description,")
                .append("mms.clickNum into form.clickNum,")
                .append("mms.sendNum into form.sendNum,")
                .append("mms.commentNum into form.commentNum,")
                .append("mms.type into form.type,")
                .append("mms.catalog into form.catalog,")
                .append("mms.createTime into form.createTime,")
                .append("mms.useNum into form.useNum,")
                .append("mms.common into form.common,")
                .append("mms.festivalDate into form.festivalDate,")
                .append("provider.name into providerName,")
                .append("user.name into form.userName")
                .append(" from Mms mms,User user,Provider provider");
        HqlBuilder builder = HqlBuilder.getInstance(hql);
        builder.andCriterion(Expression.equalProperty("user.id", "mms.users"));
        builder.andCriterion(Expression.equalProperty("mms.provider",
                "provider.id"));
        //如果user.getType()为空,则查询条件将不构造
        builder.andCriterionIgnoreNull(Expression.equal("user.type",
        user == null ? null : user.getType()));
        builder.andCriterion(Expression.equal("user.id", user==null?new Long(-1):user.getId()))
        builder.andCriterionIgnoreNull(Expression.like("mms.name", this.name,
                MatchMode.ANYWHERE));
        //当common=0时将不构造
        builder.andCriterionIgnoreZero(Expression.equal("mms.common",
                this.common));
        builder.andCriterion(Expression.like("mms.catalog",
                                              this.catalog,MatchMode.START));
        builder.andCriterion(Expression.equal("mms.status",1)));
        //添加order,group 等其他信息
        builder.other(" order by mms." + this.getOrder());
        =========================
        //定义查询返回的bean对象,可以是多个class映射
        SqlClassBean cls = new SqlClassBean("form",MyUser.class);
        //获得记录总数
        Integer count = getDao().getRecordCount(builder); 
        //获得记录,如第2个参数为空,则默认用hibernate sql,不会将查询到的值映射成自己需要的bean类型
          List data = getDao().searchByQuery(builder,cls,{第几页数},{每页几行});
              
0 请登录后投票
   发表时间:2007-03-11  
Lucas Lee谈的的确是一个重要的方面,如何尽量对一些默认操作提供方便的支持
0 请登录后投票
   发表时间:2007-03-11  
nihongye 写道
Lucas Lee谈的的确是一个重要的方面,如何尽量对一些默认操作提供方便的支持


嗯,确实可以针对这些重复的信息作默认的处理,比如这个:
<name> and o.name = :name </name>



如果标签里面只含有一个参数的话,那么我们就可以很方便满足用户的简化处理了,比如前面所举的例子:
	private static final String userHql = " <count>select count(*)</count> <list>select o</list>  from User o "
			+ " <city|depName>join o.department dep</city|depName> where 1 = 1 <name>and o.name = :name</name> <desc>and o.desc = :desc</desc>"
			+ " <sex>and o.sex = :sex</sex> <age>and o.age = :age</age> <height>and o.height = :height</height> <city>and dep.city = :city</city>"
			+ " <depName>and dep.name = :depName</depName>";

	private static final DynamicQFactory userHqlF = QT.pattern(userHql);

	private DynamicQ getQuryUserDynamicQ(UserQueryVO vo) {
		return userHqlF
				.newDynamicQ()
				.acceptTag("name", vo.getUserName() != null)
				.acceptTag("desc", vo.getUserDesc() != null)
				.acceptTag("sex", vo.getSex() != null)
				.acceptTag("age", vo.getAge() > 0)
				.acceptTag("height", vo.getHeight() > 0)
				.acceptTag("city", vo.getDepartmentCity() != null)
				.acceptTag("depName", vo.getDepartmentName() != null)
				.setParams(
						new Object[] { "name", "desc", "sex", "age", "height",
								"city", "depName" },
						new Object[] { vo.getUserName(), vo.getUserDesc(),
								vo.getSex(), new Integer(vo.getAge()),
								new Integer(vo.getHeight()),
								vo.getDepartmentCity(), vo.getDepartmentName() });
	}



可以简化为:

private static final String userHql = " <count>select count(*)</count> <list>select o</list>  from User o "
			+ " <city|depName>join o.department dep</city|depName> where 1 = 1 <name>and o.name = :name</name> <desc>and o.desc = :desc</desc>"
			+ " <sex>and o.sex = :sex</sex> <age>and o.age = :age</age> <height>and o.height = :height</height> <city>and dep.city = :city</city>"
			+ " <depName>and dep.name = :depName</depName>";

	private static final DynamicQFactory userHqlF = QT.pattern(userHql);

	private DynamicQ getQuryUserDynamicQ(UserQueryVO vo) {
		return userHqlF
				.newDynamicQ()
				.acceptTagWhenNotNull("name", vo.getUserName() )
				.acceptTagWhenNotNull("desc", vo.getUserDesc())
				.acceptTagWhenNotNull("sex", vo.getSex())
				.acceptTagWhenNotZero("age", vo.getAge() )
				.acceptTagWhenNotZero("height", vo.getHeight() )
				.acceptTagWhenNotNull("city", vo.getDepartmentCity() )
				.acceptTagWhenNotNull("depName", vo.getDepartmentName() );
	}


其中acceptTagWhenNotNull获得了属性值和如何过滤得信息.

再考虑downpour提到的意见,我们可以利用SetProperties来自动根据ParaName从vo上获得属性值,如果标签的条件也可以自动根据这些获得的属性值作一些通用的判断,那么,事情会更简单得多,比如这样:
private static final String userHql = " <count>select count(*)</count> <list>select o</list>  from User o "
			+ " <j>join o.department dep</j> where 1 = 1 <NOTNULL>and o.name = :name</NOTNULL> <NOTNULL>and o.desc = :desc</NOTNULL>"
			+ " <NOTNULL>and o.sex = :sex</NOTNULL> <GTZERO>and o.age = :age</GTZERO> <GTZERO>and o.height = :height</GTZERO> <NOTNULL>and dep.city = :city</NOTNULL>"
			+ " <NOTNULL>and dep.name = :depName</NOTNULL>";

	private static final DynamicQFactory userHqlF = QT.pattern(userHql);

	private DynamicQ getQuryUserDynamicQ(UserQueryVO vo) {
		return userHqlF
				.newDynamicQ()
				.setProperties(vo);
	}


只要几行代码就可以搞定,这里只要保证 setProperties传进去的bean的propertyname和paramName能够一一对应上就可以了。 
0 请登录后投票
   发表时间:2007-03-11  
不错。
就是这些tag有些不太爽。 

可不可以这样?

直接写ql,

"select ... where name=:name and age=:age"。

然后parser把它翻译成ast。

下面,代码生成器根据参数的值来生成代码。

String query = ...;
HashMap params = new HashMap();
params.put("name", "tom");
String hql = codegen.generateHql(query, params);


因为:age参数为null,自然就把and age=:age这个子句去掉了。
0 请登录后投票
   发表时间:2007-03-11  
ajoo 写道
不错。
就是这些tag有些不太爽。 

可不可以这样?

直接写ql,

"select ... where name=:name and age=:age"。

然后parser把它翻译成ast。

下面,代码生成器根据参数的值来生成代码。

String query = ...;
HashMap params = new HashMap();
params.put("name", "tom");
String hql = codegen.generateHql(query, params);


因为:age参数为null,自然就把and age=:age这个子句去掉了。

这些tag看起来确实不大爽,不过除此之外,我还没有找到 能够满足用户“自定值”功能的 声明性的需求的其他方式。

像你提到的方式,确实是一个好的主意。 不过如果方便用户自定制条件进行过滤的话,估计也差不多走回我们的路子。

现在,我们的DynamicQuery已经提供了更简易的方式来进行配置处理。
看这个例子:

		String hql = "select o from User o where 1=1 <NOTNULL:name>and o.name1 = :name1 </NOTNULL:name>";
		DynamicQResult dr = qt.parse(hql).setProperties(vo).generate();


NOTNULL是预定义的基于Bean访问的断言,冒号后面的是bean的属性名称。
<NOTNULL:name>and o.name1 = :name1 </NOTNULL:name> 

这句话表示 如果bean的属性:name不为空 ,则这个条件被接受,参数也随之被接受。
参数的取值 按照paramName:name1来从bean取得。 bean就是用户通过setProperties传入的参数。
翻译为表述语言就是:
如果 bean.name不为空,则接受这个条件并且将bean.name1的值作为参数名为:name1的值。


此时,可能有些人比较反感频繁的写paramName,properName,对此,我们也提供更为简便的方式,例子:
String hql = "select o from User o where 1=1 <NOTNULL>and o.name = :name </NOTNULL>";
		DynamicQResult dr = qt.parse(hql).setProperties(vo).generate();


这个例子和前面的例子,差别在于NOTNULL后面不再跟着:name ,此时NOTNULL断言针对的属性值 就是
paramName ,也就是里面的参数名:name. 翻译为表述语言就是:

如果bean.name 不为空,则接受这个条件并且将bean.name作为参数:name的值 。

前面的两个例子,总是以命名参数作为查询的参数,有些人反对了,”我喜欢用占位参数阿“,对占位符形式的查询,我们也提供了简便的支持,例子:
String hql = "select o from User o where 1=1 <NOTNULL:name>and o.name = ? </NOTNULL:name>";
		DynamicQResult dr = qt.parse(hql).setProperties(vo).generate();


参数占位符: ? 对应的参数值默认就是 根据接受条件里面的属性名:name .

这些渐变的方式,能够大大方便用户的使用.

形似:NOTNULL,NOTEMPTYSTR,GTZERO 这些特殊的符号 我们称之为 “预定义断言”,
这种断言用户完全可以自己定制使用。
0 请登录后投票
   发表时间:2007-03-11  
建议 NOTNULL 可以省略, 用 <:name> 代替 <NOTNULL:name>.

其实NOTNULL如果从语义上看, 个人觉得用 <WITH:xxx> 更恰当, 因为是用来确认该参数已指定, 才应用其内嵌内容.

另一个想法不知道是不是已经实现了, 就是判断多个参数, 最好不用嵌套的写就能实现, 比如:
<NOTNULL:age,salary,height>...</NOTNULL:age,salary,height>
而不一定非得:
<NOTNULL:age><NOTNULL:salary><NOTNULL:height>
  ...
</NOTNULL:height></NOTNULL:salary></NOTNULL:age>
0 请登录后投票
   发表时间:2007-03-11  
firebody 写道

可以简化为:

private static final String userHql = " <count>select count(*)</count> <list>select o</list>  from User o "
			+ " <city|depName>join o.department dep</city|depName> where 1 = 1 <name>and o.name = :name</name> <desc>and o.desc = :desc</desc>"
			+ " <sex>and o.sex = :sex</sex> <age>and o.age = :age</age> <height>and o.height = :height</height> <city>and dep.city = :city</city>"
			+ " <depName>and dep.name = :depName</depName>";

	private static final DynamicQFactory userHqlF = QT.pattern(userHql);

	private DynamicQ getQuryUserDynamicQ(UserQueryVO vo) {
		return userHqlF
				.newDynamicQ()
				.acceptTagWhenNotNull("name", vo.getUserName() )
				.acceptTagWhenNotNull("desc", vo.getUserDesc())
				.acceptTagWhenNotNull("sex", vo.getSex())
				.acceptTagWhenNotZero("age", vo.getAge() )
				.acceptTagWhenNotZero("height", vo.getHeight() )
				.acceptTagWhenNotNull("city", vo.getDepartmentCity() )
				.acceptTagWhenNotNull("depName", vo.getDepartmentName() );
	}


其中acceptTagWhenNotNull获得了属性值和如何过滤得信息. 


这样好多了。

现在不太爽的部分还剩SQL部分。
其实,我觉得这个问题挺简单的,这个问题并不是说满足某条件时,就加上一个查询条件,而仅仅是考虑条件值是否为null。

所以,问题在于,(以JDBC为例)条件值为空,则同时影响Prepared SQL和Parameter Setting两个部分,
即,条件值不为空,则select * from table where name=?, ps.setString(1,name);
否则:select * from table .

应该就是这么简单,只需要个简单的定义和封装就可以了。
我看到SQL里的xml tag,我觉得那里肯定增加了很多“有趣”的特性,否则不会需要这么复杂吧。
不过以我的感觉来看,我还没有找到合适的理由使用这么一个复杂的东西。

对于多条件查询来说,是一个比较简单的事情。让它保持简单吧。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics