论坛首页 Java企业应用论坛

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

浏览 37423 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-03-10  
我把原贴的内容更改为如下,觉得可能更好一些:



很多人对这个工具提出疑问,当时开发这个工具主要是基于以下这个需求:

完整的可以查看的查询语句 。
     这点,如果通过自己用if/else来组装的话,很难满足需求,最终导致查询语句被分割到代码不同部分。
hibernat提供的查询工具是通过接口的形式。虽然很灵活,但是在代码易读上带来一定的复杂度, 如果可以观看整体的查询语句,那更加好了。



为了满足的动态组装查询又可以易读 查询语句的需求,我们开发了这个小工具。
当初第一个确定的大体格式是这样子:
<count>select count(o)</count><list>select o </list>
From User u <city|depName>join u.dep dep</city|depName> where 1=1 <name>and u.name = ?</name><sex>and u.sex=?</sex><age>and u.age=?</age><depName>and dep.name=?</depName><city>and dep.city=?</city><count>group by u.name</count><list>order by u.age</list>




上面的语句就是作为一个查询的文本,可以放在任何方便管理的地方,比如DAO的static field,或者一个properties里面。

里面这些tag: count,list,city,depName,name,sex....
都是用户自己定义的标识。 用户在以后可以”赋予这些标识“以一个”是否有效的“的标识(true/false) ,
这些标签内的内容,默认是被"ignore"的(另外一种对应的模式是 ”accept"),如果什么都不作,那么直接生成的query语句就是“忽略”所有标签内的内容,结果就是:From User u  where 1=1

如果用户希望接受某个标签可以这样:
q.accept("count")  


如果用户希望在runtime时候做判断,那么可以这样
q.accept("name",vo.getName()!=null)


这里还值得一说的是: city|depName 。这里面用到了”表达式“的概念,他表示的意思是说:
如果city标签被接受或者depName标签被接受,那么这个标签里面的内容”join u.dep dep" 就被接受。


另外,我们也提供了自定义标签的扩展,如果不喜欢XML标签,而是喜欢"{}",那么可以自己声明这个标签的样式:
qt.setFilterTag("ignore","{"," "," ","}"));


然后查询语句就可以这样写:
{count select count(o) count} {list select o  list}
From User u {city|depName join u.dep dep city|depName} where 1=1 {name and u.name = ? name} ......



设置参数也是非常直白的,就是不用考虑哪些需要过滤,而是直接基于整体的语句来设置参数,比如上面的例子:
q.setParam(1,"userName").setParam(2,"femail").setParam(3,25).setParam(4,"HR").setParam(5,"BEIJING")



至于说到维护的问题,看到如此直白的语句,我相信维护还是变得更容易的。

至于说“调试”的问题,我想,还是需要充分的侧试来保证的。

另外,这个工具也就定位在这个非常小的需求上面,当然了,肯定“不合“很多人的胃口,”萝卜青菜“各有所好。  哈哈,不过,希望更多的人可以喜欢这个小工具,如果这个小工具能够为你的项目开发带来一定效率的提升,我们就更高兴了。同时也欢迎大家提出更多的意见, 我们会继续改进和完善的。


BTW:
这个小工具 我发布到 sourceforge上了。
cvs -d:pserver:anonymous@dynamicquery.cvs.sourceforge.net:/cvsroot/dynamicquery login

地址:
https://sourceforge.net/projects/dynamicquery/

大家感兴趣的话,可以下载源代码看看。里面有完整的测试样例。

如果大家觉得可以基于这个小工具来做二次开发,也欢迎下载。

      
   发表时间:2007-03-10  
嗯, 我没搞过HQL, 不过大概看明白了. 想法很不错啊, 写出来比用程序拼装清晰明了的多.

不过这一块:
                .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)   

感觉不是很必要呢, 根据parameters里面有没有指定该参数去应用不就可以了么? 怎么还要 acceptTag 呢?

另外看样子是准备兼容5.0以前的Java语法, 不然setParams()这地方用变参的话可以更清晰一些, 一个参数名后直接跟一个参数值. 分在两个数组里如果代码自动格式化大多数时候会错位.
0 请登录后投票
   发表时间:2007-03-10  
针对上面的帖子,我们来分析一下其中代码的差别


原本不使用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() });
	}


大家可以仔细对比一下。
0 请登录后投票
   发表时间:2007-03-10  
complystill 写道
嗯, 我没搞过HQL, 不过大概看明白了. 想法很不错啊, 写出来比用程序拼装清晰明了的多.

不过这一块:
                .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)   

感觉不是很必要呢, 根据parameters里面有没有指定该参数去应用不就可以了么? 怎么还要 acceptTag 呢?

另外看样子是准备兼容5.0以前的Java语法, 不然setParams()这地方用变参的话可以更清晰一些, 一个参数名后直接跟一个参数值. 分在两个数组里如果代码自动格式化大多数时候会错位.


如果是 “根据parameters里面有没有指定该参数去应用不就可以了么?”的话,那么也就意味着用户自己就得用if/else来判断哪些参数需要放到 Params里面去,这个逻辑 其实和acceptTag是一样的。
0 请登录后投票
   发表时间:2007-03-10  
引用
感觉不是很必要呢, 根据parameters里面有没有指定该参数去应用不就可以了么? 怎么还要 acceptTag 呢?

用户通过appceptTag("name",vo.vo.getUserName() != null)来告诉工具,当用户名存在的时候把and o.name = :name这段拼凑到语句中

引用
<city|depName>join o.department dep</city|depName>

city|depName表示当其中的一个存在时,那么这一个Tag也被接受.也支持&
0 请登录后投票
   发表时间:2007-03-10  
DynamicQuery对JDBC也做了完善的支持。
使用JDBC做动态查询,所要做的工作远远要比使用ORM来作动态查询的工作要多得多。

使用了DynamicQuery之后,所有的这一切都可以不用考虑,下面给出一个支持 SQL语句的例子:
public class JdbcDaoImpl {
	
	private PreparedStatement getPreparedStatement(String sql){
		return null;
	}

	// use the xml tag as the filterTag
	public static final QueryTool QT = new QueryTool(new FilterTag("ignore",
			"<", ">", "</", ">"));

	private static final String userSql = " <count>select count(*)</count> <list>select *</list>  from User o "
			+ " <city|depName>left join department dep on dep.id = user.dep_id </city|depName> where 1 = 1 <name>and o.name = ?</name> <desc>and o.desc = ?</desc>"
			+ " <sex>and o.sex = ?</sex> <age>and o.age = ?</age> <height>and o.height = ?</height> <city>and dep.city = ?</city>"
			+ " <depName>and dep.name = ?</depName>";

	private JdbcDynamicQ getQuryUserDynamicQ(UserQueryVO vo) {
		return QT.jdbcParse(userSql)
				.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).setString(1, "test")
				.setString(2, "desc").setInt(3, 1).setInt(4, 22).setInt(5, 66)
				.setString(6, "BEIJING").setString(7, "HR");
	}

	public List queryUsersByDynamicQ(UserQueryVO vo) throws SQLException {

		JdbcDynamicQResult dq = this.getQuryUserDynamicQ(vo).acceptTag("list")
				.generate();
		System.out.println(dq.getQueryStr());
		System.out.println(dq.getParams());
		//execute query
		dq.setParams(this.getPreparedStatement(dq.getQueryStr()));
		return null;

	}

	public List queryUsersCountByDynamicQ(UserQueryVO vo) throws SQLException {

		JdbcDynamicQResult dq = this.getQuryUserDynamicQ(vo).acceptTag("count")
				.generate();
		System.out.println(dq.getQueryStr());
		System.out.println(dq.getParams());
//		execute query
		dq.setParams(this.getPreparedStatement(dq.getQueryStr()));
		return null;
	}
}


注意上面的例子,我们在为SQL查询设置参数的时候,其采用的接口和 PrepareStatement的接口是完全一致的。这使得用户在 “用DynamicQuery写动态查询” 和 “直接使用 JDBC”没什么使用上的差别。

用户最后需要执行查询的时候,只要在这样调用:
dq.setParams(this.getPreparedStatement(dq.getQueryStr()));


我们再来看看其中拼写的 SQL语句:
	private static final String userSql = " <count>select count(*)</count> <list>select *</list>  from User o "
			+ " <city|depName>left join department dep on dep.id = user.dep_id </city|depName> where 1 = 1 <name>and o.name = ?</name> <desc>and o.desc = ?</desc>"
			+ " <sex>and o.sex = ?</sex> <age>and o.age = ?</age> <height>and o.height = ?</height> <city>and dep.city = ?</city>"
			+ " <depName>and dep.name = ?</depName>";


这个和平时的SQL几乎没什么差别,除了多了一些自定义的过滤标签之外。
0 请登录后投票
   发表时间:2007-03-10  
前面所有的例子,我们都使用XML标签作为拼装查询的格式 。有些用户可能对XML很反感,想换一种自定义的编写 “动它起查询语句”的格式。

DynamicQuery也提供了这样的支持 。
看下面的例子:
// use the customized as the filterTag

	public static final QueryTool QT2 = new QueryTool(new FilterTag("ignore",
			"{", " ", " ", "}"));

	public static final String HQL2 = "{c select count(*) c} {o select o o} from User o {j left join o.department dep j} "
			+ " where 1=1 {n and o.name = ?  n} {a and o.age >=?  a} {t and o.regTime = ?  t} {dn and dep.name = ? dn} ";

	public static final DynamicQFactory DF2 = QT2.pattern(HQL2);


用户自己申明使用{ } 来作为过滤标签的格式。那么编写的查询语句的格式就和 XML标签的格式大不相同。

用户可以完全根据自己的喜好来选择自己喜欢的标签。
0 请登录后投票
   发表时间:2007-03-10  
老实说,这样的写法除了在代码上看上去稍微好看一点,工作量上并没有简化多少。另外,不知道是否测试过group by和distinct,此时你的select count()貌似在语义上是有些问题吧。

另外,setParameter的接口写起来有点复杂。最好像Hibernate的Query接口中的setProperties方法那样,而不需要一一根据动态拼的HQL指定,就好了。
0 请登录后投票
   发表时间:2007-03-10  
引用
老实说,这样的写法除了在代码上看上去稍微好看一点,工作量上并没有简化多少。另外,不知道是否测试过group by和distinct,此时你的select count()貌似在语义上是有些问题吧。

另外,setParameter的接口写起来有点复杂。最好像Hibernate的Query接口中的setProperties方法那样,而不需要一一根据动态拼的HQL指定,就好了。

思考量肯定是减少了.你注意下面这段
引用
# 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()); 
#         } 

这要费点脑筋写吧.
而如果使用acceptTag的方式就简单多了,只要考虑需要的tag就可以了.整个写代码的过程都很自然.
另外group by和distinct这些都不会受工具的影响.工具只要求对sql做标注.
0 请登录后投票
   发表时间:2007-03-10  
downpour 写道
老实说,这样的写法除了在代码上看上去稍微好看一点,工作量上并没有简化多少。另外,不知道是否测试过group by和distinct,此时你的select count()貌似在语义上是有些问题吧。

另外,setParameter的接口写起来有点复杂。最好像Hibernate的Query接口中的setProperties方法那样,而不需要一一根据动态拼的HQL指定,就好了。


那个setParams是为批量配对设置参数使用的,其实更一般的接口是和hibernate/jpa的设置参数的接口是一致的。我们也提供了setParam(Object paramId,Object paramValue)的接口,只不过这个例子没有使用这个接口而已。


0 请登录后投票
论坛首页 Java企业应用版

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