论坛首页 Java企业应用论坛

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

浏览 37430 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-03-12  
我一看到语句里的
引用
"select o from User o where 1=1 <NOTNULL:name>and o.name1 = :name1 </NOTNULL:name>"

头皮就发麻. 老天..
0 请登录后投票
   发表时间:2007-03-12  
XMLDB 写道
总之,我一看到语句里的
引用
"select o from User o where 1=1 <NOTNULL:name>and o.name1 = :name1 </NOTNULL:name>"

头皮就发麻.


不使用“便捷”方式,使用通用的方式,SQL语句如下:

 select o from User o where 1=1 <n>[/b]and o.name1 = :name1 </n>


这个你感觉如何? 上面的方式确实比较“不大友好”,这是为了方便用户写频繁的代码而设计,可能并不真正符合用户的需求,反正我就很少使用到。

如果你对xml标签反感,换成这样:

select o from User o where 1=1 {n and o.name1 = :name1 n}


这样如何?
不行的话,这样:

select o from User o where 1=1 [n and o.name1 = :name1 n]


当时考虑到不同用户的喜好, 标签都是可以声明的。
0 请登录后投票
   发表时间:2007-03-12  
dada 写道
ajoo 写道


两点想法:
1。自定义断言的需求是否频繁到值得去影响不需要预定义断言的那大部分用户的易用性?可不可以考虑只处理90%的一般情况?(就象rails干脆不支持复合key一样?)
2。这个自定义断言难道不能在参数值上下功夫?毕竟sql最好保持sql,逻辑还是在java里为妙。比如:
String hql = "select o from User o where o.name = $name";
HashMap params = new HashMap();
params.put("name", notEmptyStr(name));
DynamicQResult dr = qt.generate(hql, params);


让notEmptyStr在string为空的时候返回null或者一个你们自己特定的Null Object可行么?


这个和查询语句绑的太紧了,可能影响动态生成结果的不一定是查询语句上的值。


只要大多数动态查询是基于语句上的值就可以了。少数的特殊情况,直接拼sql好了,又不会死人。
0 请登录后投票
   发表时间:2007-03-12  
看来大家都对这个Tools很感兴趣啊。我再发表一些个人的想法吧。

上面有不少人对firebody坚持用<n></n>或者{n ..  n}这种类XML的SQL模板有点异议,觉得有点繁琐。其实我倒是倾向这种SQL模板,不过我的思路和firebody有点不一样,我觉得DynamicQuery的查询条件不应该拼在这个SQL模板中,而是用一种命名来代替。

SELECT count(*) FROM User user WHERE 1 = 1 ${:userName} ${:userPassword}


用一种命名约束来自动生成相应的DynamicQuery条件,例如:

<input type="text" name="eq.user.name">
<input type="text" name="like.user.password">


然后将前台收集的所有的这些提交参数压到一个Map中,再和相应的SQL模板merge一下得到相应SQL语句,在merge的过程中,把提交的参数为null的条件过滤掉。结果的SQL语句如下:

SELECT count(*) FROM User user WHERE 1 = 1 AND user.name = :userName AND user.password LIKE :userPassword


然后再结合一个类似setProperties的设置参数的方法,整个查询就会比较简单,整个API的设计也与ajoo所说的比较一致了。

另外,如果采用webwork或者struts2这种前台的web框架,完全可以把我前面所说的eq.user.name等命名约定放到一个拦截器里面统一处理,这样整个接口就比较清晰可见了,流程也比较清晰。事实上,即使没有这样的前台框架,这些API甚至可以直接调用,只要我们自己去构造那个传入的Map就可以了,搞个Utils直接搞定。
0 请登录后投票
   发表时间:2007-03-12  
firebody 写道
针对上面的帖子,我们来分析一下其中代码的差别


原本不使用DynamicQuery的组装动态查询的主要逻辑如下:
private String getJoinWhereFragment(UserQueryVO vo, Map params) {
		StringBuffer joinFragment = new StringBuffer();
		StringBuffer whereFragment = new StringBuffer(" where 1 = 1 ");
		if (vo.getUserName() != null) {
			whereFragment.append(" and o.name = :name ");
			params.put("name", vo.getUserName());
		}
		if (vo.getUserDesc() != null) {
			whereFragment.append(" and o.desc = :desc ");
			params.put("desc", vo.getUserDesc());
		}
		if (vo.getAge() > 0) {
			whereFragment.append(" and o.age = :age ");
			params.put("age", new Integer(vo.getAge()));
		}
		if (vo.getSex() != null) {
			whereFragment.append(" and o.sex = :sex ");
			params.put("sex", vo.getSex());
		}
		if (vo.getHeight() > 0) {
			whereFragment.append(" and o.height = :height ");
			params.put("height", new Integer(vo.getHeight()));
		}
		if (vo.getDepartmentCity() != null) {
			joinFragment.append(" join o.department dep ");
			whereFragment.append(" and dep.city = :city ");
			params.put("city", vo.getDepartmentCity());
		}
		if (vo.getDepartmentName() != null) {
			if (joinFragment.toString().indexOf("o.department") == -1)
				joinFragment.append(" join o.department dep ");
			whereFragment.append(" and dep.name = :depName ");
			params.put("depName", vo.getDepartmentName());
		}
		joinFragment.append(whereFragment.toString());
		return joinFragment.toString();
	}

从上面的代码可以看出,即使在代码封装上下了足够的功夫,代码仍然充斥着众多的if/else语句,而且完整的HQL语句也被分割到各个代码段里面去了。


用了DynamicQuery的拼装动态查询的主要代码如下:

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() });
	}


大家可以仔细对比一下。



其实还有个问题需要考虑,就是代码的可读性!2者之间的比较结果也很明显
0 请登录后投票
   发表时间:2007-03-12  
ajoo 写道
Lucas Lee 写道
ajoo 写道

2。这个自定义断言难道不能在参数值上下功夫?毕竟sql最好保持sql,逻辑还是在java里为妙。比如:
String hql = "select o from User o where o.name = $name";
HashMap params = new HashMap();
params.put("name", notEmptyStr(name));
DynamicQResult dr = qt.generate(hql, params);


让notEmptyStr在string为空的时候返回null或者一个你们自己特定的Null Object可行么?


返回null对于SQL而言应该不合适。
1.增大了不必要的SQL解析和运算,以及使SQL不那么清晰。
2.Null或者类似对象在SQL中不能用=、like等常规操作符号比较,一般只有is null、is not null

1。不认为这是不必要的。哪里不清晰了?
2。你似乎误会了。这个null是给qt.generate()这个函数用的。当它发现某个参数是null或者NullObject的时候,直接省略掉使用这个变量的sql子句。这样做的目的是可以支持这种动态sql,而同时又不必在sql中引入标签。
例子中的sql如果name为null,那么生成的最终sql会是:
"select o from User o",没有null什么事。



这个IDEA不错,考虑以后的版本兼容这个特性。

不过,有些问题:
<count>select count(*)</count><list>select o</list> From User o .....

比如上面,因为希望可以重用后面的查询条件,所以放到了一起,通过标签 count,list来选择过滤,如果不使用标签,有什么更好的方式吗?
另外,如果ajoo你感兴趣和有空的话,也欢迎你加入进来,实现你自己想要的特性阿。 嘿嘿

想当年,我也是这么把“nihongye"拉进来,哈哈,让他和我一起把JPA开发了出来。
这样的远程合作蛮有意思的,我们还远程结对开发过,哈哈,太有意思了。

哎,不过随着工作的忙碌,很多事情都匆匆而去。 都为生活而奔波啦。
0 请登录后投票
   发表时间:2007-03-12  
我来ps:
   1.where 后面11=1 smell bad!
   2.何必需要标签来控制  去掉<city|depName> 等,难道就不能实 现?例如select user.* from user where user.name=:name;
       map.put("name",value);
   根据map里的value值,如果为空,重新生成select user.* from user ;
   
    3.有时if,else也有存在的必要;用标签控制还是逻辑很麻烦吧?
   例如:
      user(id,name),account(id,no),user2account(id,userid,accountid);
   查询功能:查找满足条件的用户,能根据no,和name过滤;
    可见,如果no不为空的话,sql如下:
   select user.* from user,account,user2account where user2account.accountid = account.id and account.no = ? and user.name =?;(当然,你可以说当no为空的时候,sql也做关联,有必要?
    如果no为空的话
      select user.* from user where user.name=?;
   结论:某中程度上,会简易开发 但还不太理想;
   
0 请登录后投票
   发表时间:2007-03-12  
ahau205109 写道
我来ps:
   1.where 后面11=1 smell bad!
   2.何必需要标签来控制  去掉<city|depName> 等,难道就不能实 现?例如select user.* from user where user.name=:name;
       map.put("name",value);
   根据map里的value值,如果为空,重新生成select user.* from user ;
   
    3.有时if,else也有存在的必要;用标签控制还是逻辑很麻烦吧?
   例如:
      user(id,name),account(id,no),user2account(id,userid,accountid);
   查询功能:查找满足条件的用户,能根据no,和name过滤;
    可见,如果no不为空的话,sql如下:
   select user.* from user,account,user2account where user2account.accountid = account.id and account.no = ? and user.name =?;(当然,你可以说当no为空的时候,sql也做关联,有必要?
    如果no为空的话
      select user.* from user where user.name=?;
   结论:某中程度上,会简易开发 但还不太理想;
   

至于那个city|depName ,用户自己可以做选择不要 或者 给一个更优雅的别名:j = city|depName。 有些join 太多,会严重影响性能的。

你的例子 应该不难做吧:

   select user.* from user <no>,account,user2account</no> where <no>user2account.accountid = account.id and account.no = ? and </no> user.name =?;
qt.parse(sql).accept("no",no!=null).setParam(.....).....


no标签里面的内容/参数 可能被过滤到。
0 请登录后投票
   发表时间:2007-03-12  
firebody 写道

不过,有些问题:
<count>select count(*)</count><list>select o</list> From User o .....

比如上面,因为希望可以重用后面的查询条件,所以放到了一起,通过标签 count,list来选择过滤,如果不使用标签,有什么更好的方式吗?
另外,如果ajoo你感兴趣和有空的话,也欢迎你加入进来,实现你自己想要的特性阿。 嘿嘿


这种东西我是认为应该用jrc来解决的。直接把字符串放在一起反而很不自然。
Relation base = parse("select * from User o ...");
Relation query = base.project(useCount?"count(*)":"o");


或者就是直接字符串拼接也好啊。

我倒是挺感兴趣加入的。如果咱们能对设计取得一定的共识的话。我的想法是把jrc的parser扒过来,然后处理这个ast。或者把jrc和你的项目合并也行。(不过jrc的parser用比较严格的sql-92语法,不支持"from a,b,c"这种。不知道是否有必要花精力增加对这个语法的支持)
0 请登录后投票
   发表时间:2007-03-12  
ahau205109 写道
我来ps:
   1.where 后面11=1 smell bad!
   2.何必需要标签来控制  去掉<city|depName> 等,难道就不能实 现?例如select user.* from user where user.name=:name;
       map.put("name",value);
   根据map里的value值,如果为空,重新生成select user.* from user ;
   
    3.有时if,else也有存在的必要;用标签控制还是逻辑很麻烦吧?
   例如:
      user(id,name),account(id,no),user2account(id,userid,accountid);
   查询功能:查找满足条件的用户,能根据no,和name过滤;
    可见,如果no不为空的话,sql如下:
   select user.* from user,account,user2account where user2account.accountid = account.id and account.no = ? and user.name =?;(当然,你可以说当no为空的时候,sql也做关联,有必要?
    如果no为空的话
      select user.* from user where user.name=?;
   结论:某中程度上,会简易开发 但还不太理想;
   


可以考虑这样做:
String sql = "select u.* from user u
inner join $user2account ua on
ua.userid = u.userid
inner join $account a on
a.accountid = ua.accountid and a.accountid=$accountid
where user.name=$username"

HashMap params = new HashMap();
params.put("username", userName);
if(accountId!=null) {
  params.put("accountid", accountId);
  params.put("user2account", "user2account");
  params.put("account", "account");
}


这样在accountId是null的时候,两个inner join因为两个table变量没有定义,都会被query translator给忽略掉。
0 请登录后投票
论坛首页 Java企业应用版

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