|
该帖已经被评为良好帖
|
|
|---|---|
| 作者 | 正文 |
|
最后更新时间:2007-03-12 关键字: J2EE
我把原贴的内容更改为如下,觉得可能更好一些:
很多人对这个工具提出疑问,当时开发这个工具主要是基于以下这个需求: 完整的可以查看的查询语句 。 这点,如果通过自己用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/ 大家感兴趣的话,可以下载源代码看看。里面有完整的测试样例。 如果大家觉得可以基于这个小工具来做二次开发,也欢迎下载。 声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
|
|
| 返回顶楼 | |
|
最后更新时间: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()这地方用变参的话可以更清晰一些, 一个参数名后直接跟一个参数值. 分在两个数组里如果代码自动格式化大多数时候会错位. |
|
| 返回顶楼 | |
|
最后更新时间: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() });
}
大家可以仔细对比一下。 |
|
| 返回顶楼 | |
|
最后更新时间: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是一样的。 |
|
| 返回顶楼 | |
|
最后更新时间: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也被接受.也支持& |
|
| 返回顶楼 | |
|
最后更新时间: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几乎没什么差别,除了多了一些自定义的过滤标签之外。 |
|
| 返回顶楼 | |
|
最后更新时间: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标签的格式大不相同。 用户可以完全根据自己的喜好来选择自己喜欢的标签。 |
|
| 返回顶楼 | |
|
最后更新时间:2007-03-10
老实说,这样的写法除了在代码上看上去稍微好看一点,工作量上并没有简化多少。另外,不知道是否测试过group by和distinct,此时你的select count()貌似在语义上是有些问题吧。
另外,setParameter的接口写起来有点复杂。最好像Hibernate的Query接口中的setProperties方法那样,而不需要一一根据动态拼的HQL指定,就好了。 |
|
| 返回顶楼 | |
|
最后更新时间: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做标注. |
|
| 返回顶楼 | |
|
最后更新时间:2007-03-10
downpour 写道 老实说,这样的写法除了在代码上看上去稍微好看一点,工作量上并没有简化多少。另外,不知道是否测试过group by和distinct,此时你的select count()貌似在语义上是有些问题吧。
另外,setParameter的接口写起来有点复杂。最好像Hibernate的Query接口中的setProperties方法那样,而不需要一一根据动态拼的HQL指定,就好了。 那个setParams是为批量配对设置参数使用的,其实更一般的接口是和hibernate/jpa的设置参数的接口是一致的。我们也提供了setParam(Object paramId,Object paramValue)的接口,只不过这个例子没有使用这个接口而已。 |
|
| 返回顶楼 | |









