2008-01-03
5种DAO查询方法的签名方式,哪个更好?
关键字: dao方法签名
DAO类中查询方法的签名多种多样,大家都使用什么签名方法呢?拿出来讨论一下吧。
DAO层除了CRUD的数据操作外,另一个重要的操作就是根据查询条件执行数据查询,不同的ORM框架都允许用户动态绑定参数确定查询条件。查询条件项的数目往往是不固定的,如既可能要求以userName为条件查询User,也可能要求以userName+status等组合条件查询User。条件项数目的不定性给查询接口方法的设计造成为一定的困难,实体DAO定义带参的查询方法时,一般有5种方式,下面分别对这些方法进行介绍。
方式1:每一个条件项参数对应一个入参
查询方法中为每一个查询条件项定义一个对应的入参,如:
这种方法签名含义清晰,可读性强,内部的逻辑处理简单,但接口稳定性差。
假如这个findOrder()方法需要添加一个userName的条件,有两种重构的方式:第一,调整方法的签名,新增一个String userName入参,这种重构被认为是不健康的重构,因为它违反软件设计中经典的“开-闭原则”,此外如果条件项个数很多,方法签名将显得过于拖沓;第二,在DAO类中新增一个重载查询方法,如果查询条件项的组合数过多,DAO类的方法数目将直线上升,整个实体DAO类将显得臃肿笨重。
方式2:使用数组传递条件项参数
通过数组的方式传递查询条件项参数,由于参数类型的不一致性,要求数组类型采用Object[]:
这种方法接口可以应付查询条件项参数组合的多样性并保持接口的稳定性,开发者必须在方法内部从数组中获取参数再传递给查询引擎。缺点是方法的可读性不强,调用者往往需要通过查看接口对应的Javadoc才能正确使用。此外,在JDK 5.0以下的版本中,因为没有自动拆/解包的语法特性,调用前必须对基本类型的参数使用封装类封装,有时在方法内部还需要进行相反的过程,在使用上较为不便。不过由于Spring为支持的ORM框架都提供了类似的查询接口(如HibernateTemplate#find(String queryString, Object[] values)),所以DAO方法内部的处理相对还是比较简单的。
方式3:使用JDK 5.0的不定参数
如果在JDK 5.0中,则可以采用不定参数进行方法签名,这种方式在逻辑上和方式2并无多大的区别:
方式4:将查询条件项参数封装成对象
为了提高方法1中方法签名简洁性,增强方法2、3中方法签名的可读性,方式4提出将查询条件项参数封装成一个对象的思路,查询方法使用查询条件对象传递查询条件:
OrderQueryParam查询条件对象封装了hql查询语句可能会用到的条件项参数,在查询方法内部,开发者必须判断查询条件对象的属性并正确绑定条件项参数。由于需要为条件项参数定义一个类,因此会造成类数目的膨胀,有时甚至一个实体DAO需要对应多个查询条件参数类。另外,当需要添加一个新的条件项参数时,条件封装类还需要进行相应调整。
方式5:使用Map传递条件项参数
另一种被广泛使用的方法是采用Map传递条件项参数,键为参数名,值为参数值:
使用这种方式,接口方法签名可以在条件项发生变化的情况下保持稳定,同时通过键指定条件项参数名在一定程度上解决了接口的健壮性(当调用者指定错误参数名时容易得到错误报警)。但和方法2、3一样调用者必须通过接口Javadoc才能明白不同条件项要以什么名称进行设置。
注:以上5个查询方法签名的总结摘自于《精通Spring 2.x--企业应用开发详解》
DAO层除了CRUD的数据操作外,另一个重要的操作就是根据查询条件执行数据查询,不同的ORM框架都允许用户动态绑定参数确定查询条件。查询条件项的数目往往是不固定的,如既可能要求以userName为条件查询User,也可能要求以userName+status等组合条件查询User。条件项数目的不定性给查询接口方法的设计造成为一定的困难,实体DAO定义带参的查询方法时,一般有5种方式,下面分别对这些方法进行介绍。
方式1:每一个条件项参数对应一个入参
查询方法中为每一个查询条件项定义一个对应的入参,如:
List findOrder(String hql,Date startTime,Date endTime,int deptId)
这种方法签名含义清晰,可读性强,内部的逻辑处理简单,但接口稳定性差。
假如这个findOrder()方法需要添加一个userName的条件,有两种重构的方式:第一,调整方法的签名,新增一个String userName入参,这种重构被认为是不健康的重构,因为它违反软件设计中经典的“开-闭原则”,此外如果条件项个数很多,方法签名将显得过于拖沓;第二,在DAO类中新增一个重载查询方法,如果查询条件项的组合数过多,DAO类的方法数目将直线上升,整个实体DAO类将显得臃肿笨重。
方式2:使用数组传递条件项参数
通过数组的方式传递查询条件项参数,由于参数类型的不一致性,要求数组类型采用Object[]:
List findOrder(String hql,Object[] params)
这种方法接口可以应付查询条件项参数组合的多样性并保持接口的稳定性,开发者必须在方法内部从数组中获取参数再传递给查询引擎。缺点是方法的可读性不强,调用者往往需要通过查看接口对应的Javadoc才能正确使用。此外,在JDK 5.0以下的版本中,因为没有自动拆/解包的语法特性,调用前必须对基本类型的参数使用封装类封装,有时在方法内部还需要进行相反的过程,在使用上较为不便。不过由于Spring为支持的ORM框架都提供了类似的查询接口(如HibernateTemplate#find(String queryString, Object[] values)),所以DAO方法内部的处理相对还是比较简单的。
方式3:使用JDK 5.0的不定参数
如果在JDK 5.0中,则可以采用不定参数进行方法签名,这种方式在逻辑上和方式2并无多大的区别:
List findOrder(String hql,Object... params)
方式4:将查询条件项参数封装成对象
为了提高方法1中方法签名简洁性,增强方法2、3中方法签名的可读性,方式4提出将查询条件项参数封装成一个对象的思路,查询方法使用查询条件对象传递查询条件:
List<Order> findOrder(String hql,OrderQueryParam param)
OrderQueryParam查询条件对象封装了hql查询语句可能会用到的条件项参数,在查询方法内部,开发者必须判断查询条件对象的属性并正确绑定条件项参数。由于需要为条件项参数定义一个类,因此会造成类数目的膨胀,有时甚至一个实体DAO需要对应多个查询条件参数类。另外,当需要添加一个新的条件项参数时,条件封装类还需要进行相应调整。
方式5:使用Map传递条件项参数
另一种被广泛使用的方法是采用Map传递条件项参数,键为参数名,值为参数值:
List<Order> findOrder(String hql,Map params)
使用这种方式,接口方法签名可以在条件项发生变化的情况下保持稳定,同时通过键指定条件项参数名在一定程度上解决了接口的健壮性(当调用者指定错误参数名时容易得到错误报警)。但和方法2、3一样调用者必须通过接口Javadoc才能明白不同条件项要以什么名称进行设置。
注:以上5个查询方法签名的总结摘自于《精通Spring 2.x--企业应用开发详解》
- 15:23
- 浏览 (646)
- 论坛浏览 (6131)
- 评论 (22)
- 分类: Spring
- 相关推荐
评论
tianhen 写道
我们一般是写了一个分页查询的方法:
List pagedQuery(Page page, Where where);
where条件是一个条件树,可以任意进行组合完成复杂的条件查询。
List pagedQuery(Page page, Where where);
where条件是一个条件树,可以任意进行组合完成复杂的条件查询。
你这种用Where的写法,会不会在安全上有问题呢,这样的话,任何一个程序员都可以操作这个表了,一下子就把数据层暴露了,虽然是select,但个人觉得为了安全,应该一个条件写一个方法~
tianhen 写道
Where where = Where.rootWhere("prop",Where.EQ,object);
多条件查询怎么搞啊? 及有 and、or
我目前的写法,只是感到不够灵活,到不够用的时候再想办法重构好了。。
@Target( { ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLQuery {
/**
* 与查询参数对应的SQL片段的数组<br>
* 数组长度=1:此SQL片段被插入到SQL查询语句中,实际参数值添加到与?对应的位置。<br>
* 数组长度=2:此SQL片段被插入到SQL查询语句中,实际参数类型应该是Boolean,<br>
* 当实参=true,插入第一个SQL片段,实参=false,插入第二个SQL片段
*
* @return
*/
String[] value() default {};
}
public class QueryBuilder {
// 生成的SQL语句
StringBuilder sql = new StringBuilder();
// sql中与‘?’匹配的参数
List<Object> params = new ArrayList<Object>();
/**
* 构建查询语句<br>
*
* 例子:QueryBuilder builder = new QueryBuilder();<br>
* builder.setSql(buf);<br>
* builder.setParams(params);<br>
* builder.build(QueryArgs.class, args);
*
* @param clazz
* 用注解标识的查询参数类
* @param args
* 查询参数对象,可以是查询参数类的实例,<br>
* 也可以仅仅是一个map,只要符合OGNL读取数据的方式。
* @return
*/
public StringBuilder build(Class<?> clazz, Object args) {
// 如果没有传递实际参数,则忽略此次构建
if (args == null)
return sql;
for (Field field : clazz.getDeclaredFields()) {
SQLQuery query = field.getAnnotation(SQLQuery.class);
// 非查询字段,跳到下一个
if (query == null)
continue;
try {
Object value = Ognl.getValue(field.getName(), args);
// 查询字段空缺的,跳到下一个
if (value == null
|| (value instanceof String && StringUtils.isEmpty(value.toString())))
continue;
// query.value().length所表示的含义参考SQLQuery注解
if (query.value().length == 1) {
sql.append(query.value()[0]);
params.add(value);
} else if (query.value().length >= 2)
sql.append(Boolean.valueOf(value.toString()) ? query.value()[0]
: query.value()[1]);
// query.value().length==0,跳到下一个
} catch (Exception e) {
log.warn("处理查询参数失败,该参数被忽略:" + field.getName());
continue;
}
}
return sql;
}
//省略其他部分。。。。
}
public class QueryArgs {
// 受理码
@SQLQuery(" and a.acceptCode=?")
String acceptCode;
// 项目类型
@SQLQuery(" and a.definition.id=?")
Long definitionId;
// 申请人姓名,模糊查询
@SQLQuery(" and a.proposer.name like '%'+?+'%'")
String proposerName;
// 受理时间范围1
@SQLQuery(" and a.acceptTime >= ?")
Date beginTime;
// 受理时间范围2
@SQLQuery(" and a.acceptTime <= ?")
Date endTime;
// 是否已经办结,如果未指定,则包括全部项目
@SQLQuery( { " and a.finishedTime is not null", " and a.finishedTime is null" })
Boolean m_finished;
// 假想的finished参数,供页面使用,
// 因为struts2将0长度字符串传入Boolean的时候,转成了false,下面是为解决这个问题
public void setFinished(String finished) {
m_finished = StringUtils.isEmpty(finished) ? null : Boolean.valueOf(finished);
}
// 承办单位id
@SQLQuery(" and a.definition.department.id=?")
Long departmentId;
//省略若干getter、setter。。。
}
我目前的写法,只是感到不够灵活,到不够用的时候再想办法重构好了。。
@Target( { ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLQuery {
/**
* 与查询参数对应的SQL片段的数组<br>
* 数组长度=1:此SQL片段被插入到SQL查询语句中,实际参数值添加到与?对应的位置。<br>
* 数组长度=2:此SQL片段被插入到SQL查询语句中,实际参数类型应该是Boolean,<br>
* 当实参=true,插入第一个SQL片段,实参=false,插入第二个SQL片段
*
* @return
*/
String[] value() default {};
}
public class QueryBuilder {
// 生成的SQL语句
StringBuilder sql = new StringBuilder();
// sql中与‘?’匹配的参数
List<Object> params = new ArrayList<Object>();
/**
* 构建查询语句<br>
*
* 例子:QueryBuilder builder = new QueryBuilder();<br>
* builder.setSql(buf);<br>
* builder.setParams(params);<br>
* builder.build(QueryArgs.class, args);
*
* @param clazz
* 用注解标识的查询参数类
* @param args
* 查询参数对象,可以是查询参数类的实例,<br>
* 也可以仅仅是一个map,只要符合OGNL读取数据的方式。
* @return
*/
public StringBuilder build(Class<?> clazz, Object args) {
// 如果没有传递实际参数,则忽略此次构建
if (args == null)
return sql;
for (Field field : clazz.getDeclaredFields()) {
SQLQuery query = field.getAnnotation(SQLQuery.class);
// 非查询字段,跳到下一个
if (query == null)
continue;
try {
Object value = Ognl.getValue(field.getName(), args);
// 查询字段空缺的,跳到下一个
if (value == null
|| (value instanceof String && StringUtils.isEmpty(value.toString())))
continue;
// query.value().length所表示的含义参考SQLQuery注解
if (query.value().length == 1) {
sql.append(query.value()[0]);
params.add(value);
} else if (query.value().length >= 2)
sql.append(Boolean.valueOf(value.toString()) ? query.value()[0]
: query.value()[1]);
// query.value().length==0,跳到下一个
} catch (Exception e) {
log.warn("处理查询参数失败,该参数被忽略:" + field.getName());
continue;
}
}
return sql;
}
//省略其他部分。。。。
}
public class QueryArgs {
// 受理码
@SQLQuery(" and a.acceptCode=?")
String acceptCode;
// 项目类型
@SQLQuery(" and a.definition.id=?")
Long definitionId;
// 申请人姓名,模糊查询
@SQLQuery(" and a.proposer.name like '%'+?+'%'")
String proposerName;
// 受理时间范围1
@SQLQuery(" and a.acceptTime >= ?")
Date beginTime;
// 受理时间范围2
@SQLQuery(" and a.acceptTime <= ?")
Date endTime;
// 是否已经办结,如果未指定,则包括全部项目
@SQLQuery( { " and a.finishedTime is not null", " and a.finishedTime is null" })
Boolean m_finished;
// 假想的finished参数,供页面使用,
// 因为struts2将0长度字符串传入Boolean的时候,转成了false,下面是为解决这个问题
public void setFinished(String finished) {
m_finished = StringUtils.isEmpty(finished) ? null : Boolean.valueOf(finished);
}
// 承办单位id
@SQLQuery(" and a.definition.department.id=?")
Long departmentId;
//省略若干getter、setter。。。
}
List<Order> orderList = new service.queryList(Order.class,new HashMap(){{
put("type.name","=新订单");
put("createdDate","bewteen 2008-01-01 and 2008-01-30");
put("orderBy.name","=张三");
}});
写一个CreteriaUtils将 =,>,like条件等转换为hibernate的Creteria
spiritfrog
2008-01-26
回复
stamen 写道
DAO层除了CRUD的数据操作外,另一个重要的操作就是根据查询条件执行数据查询,不同的ORM框架都允许用户动态绑定参数确定查询条件。查询条件项的数目往往是不固定的,如既可能要求以userName为条件查询User,也可能要求以userName+status等组合条件查询User。条件项数目的不定性给查询接口方法的设计造成为一定的困难,实体DAO定义带参的查询方法时,一般有5种方式,下面分别对这些方法进行介绍。
参数不稳定,同样你的语句也会不稳定啊,比如userName+status的情况,就会成为where userName=? or status=?,楼主怎么没有讨论这种情况呢?这样的话,方法中传入语句就不行了。再者,你的查找方法为什么要传入语句呢,像List findOrder(String hql,Object[] params),难道你还在DAO的外面去拼sql?而且怎么看findOrder也不是basedao里的,传入语句就没有道理了。楼主最好说清楚这里讨论的是basedao还是继承的dao
我觉得楼主讨论的更像是DAO的查询方法的参数该怎样写,既清晰又简单。根据以上分析,应该变成这样:
List findOrder(String hql,Object[] params) --> List findOrder(Object[] params)
方法里面,就可以根据情况使用hql、sql或Criteria
至于参数哪种好,我觉得除了那个参数一一写出来的,其他都还好
b051 写道
要说查询条件的对象化, 那肯定是4.
1,2,3没有什么区别, 5只是在namedparam比较有用. 问题是有的, 但我如果写dao的话, 还是会用3.
要说内存占用和工作效率, 多半应该是3好于4.
我认为真正的dao是没有业务逻辑,只是怎么和数据库对话而已. 在已经有了hibernate/jpa/jdbctemplate的情况下, 这种东西实在没必要人去做. 除非数据库压根没可能对象化.
撇开dao不谈, 要说查询的方式, 我喜欢这样的方式:
public interface PersonAccess {
@Finder(query="from Person where firstName = :firstName")
Person find(@Named("firstName") String name);
}
翻页:
public interface PersonAccess {
@Finder(query="from Person")
List<Person> listAll(@FirstResult int first, @MaxResults int max);
}
接口已经完全说明怎么查询了,其实现的单例动态生成就可以了.asm+guice/cglib.
(http://www.wideplay.com/dynamicfinders)
1,2,3没有什么区别, 5只是在namedparam比较有用. 问题是有的, 但我如果写dao的话, 还是会用3.
要说内存占用和工作效率, 多半应该是3好于4.
我认为真正的dao是没有业务逻辑,只是怎么和数据库对话而已. 在已经有了hibernate/jpa/jdbctemplate的情况下, 这种东西实在没必要人去做. 除非数据库压根没可能对象化.
撇开dao不谈, 要说查询的方式, 我喜欢这样的方式:
public interface PersonAccess {
@Finder(query="from Person where firstName = :firstName")
Person find(@Named("firstName") String name);
}
翻页:
public interface PersonAccess {
@Finder(query="from Person")
List<Person> listAll(@FirstResult int first, @MaxResults int max);
}
接口已经完全说明怎么查询了,其实现的单例动态生成就可以了.asm+guice/cglib.
(http://www.wideplay.com/dynamicfinders)
翻页查询我一般直接用JDBC,这样更灵活更高效,一般只对单条记录操作使用Hibernate,在实际应用中我发觉这种方式挺适用的。
要说查询条件的对象化, 那肯定是4.
1,2,3没有什么区别, 5只是在namedparam比较有用. 问题是有的, 但我如果写dao的话, 还是会用3.
要说内存占用和工作效率, 多半应该是3好于4.
我认为真正的dao是没有业务逻辑,只是怎么和数据库对话而已. 在已经有了hibernate/jpa/jdbctemplate的情况下, 这种东西实在没必要人去做. 除非数据库压根没可能对象化.
撇开dao不谈, 要说查询的方式, 我喜欢这样的方式:
public interface PersonAccess {
@Finder(query="from Person where firstName = :firstName")
Person find(@Named("firstName") String name);
}
翻页:
public interface PersonAccess {
@Finder(query="from Person")
List<Person> listAll(@FirstResult int first, @MaxResults int max);
}
接口已经完全说明怎么查询了,其实现的单例动态生成就可以了.asm+guice/cglib.
(http://www.wideplay.com/dynamicfinders)
1,2,3没有什么区别, 5只是在namedparam比较有用. 问题是有的, 但我如果写dao的话, 还是会用3.
要说内存占用和工作效率, 多半应该是3好于4.
我认为真正的dao是没有业务逻辑,只是怎么和数据库对话而已. 在已经有了hibernate/jpa/jdbctemplate的情况下, 这种东西实在没必要人去做. 除非数据库压根没可能对象化.
撇开dao不谈, 要说查询的方式, 我喜欢这样的方式:
public interface PersonAccess {
@Finder(query="from Person where firstName = :firstName")
Person find(@Named("firstName") String name);
}
翻页:
public interface PersonAccess {
@Finder(query="from Person")
List<Person> listAll(@FirstResult int first, @MaxResults int max);
}
接口已经完全说明怎么查询了,其实现的单例动态生成就可以了.asm+guice/cglib.
(http://www.wideplay.com/dynamicfinders)
EXvision 写道
stamen 写道
tianhen 写道
Where where = Where.rootWhere("prop",Where.EQ,object);
这种方式挺有创意的,也相当灵活,相当不错!
Hibernate似乎有类似的组件吧。。。当然了不用Hibernate就是另外一码事。。。
他的灵感来源于hibernate的Criteria
Criteria更好.
and(
eq("thisDN",750),
gt("size",5)
or(
eq("otherDN",110),
like("name","%Rason%")
)
)
and(
eq("thisDN",750),
gt("size",5)
or(
eq("otherDN",110),
like("name","%Rason%")
)
)
stamen 写道
tianhen 写道
Where where = Where.rootWhere("prop",Where.EQ,object);
这种方式挺有创意的,也相当灵活,相当不错!
Hibernate似乎有类似的组件吧。。。当然了不用Hibernate就是另外一码事。。。
tianhen 写道
Where where = Where.rootWhere("prop",Where.EQ,object);
这种方式挺有创意的,也相当灵活,相当不错!
最近加入圈子
最新评论
-
5种DAO查询方法的签名方 ...
记得以前进行java培训时候,学的就是dao,可惜工作了也就不用这个了,现在用s ...
-- by 01071405 -
5种DAO查询方法的签名方 ...
tianhen 写道我们一般是写了一个分页查询的方法: List ...
-- by MrLee23 -
5种DAO查询方法的签名方 ...
tianhen 写道 Where where = Where.rootWhere ...
-- by realorg -
直接使用Junit测试Spring ...
引用3)数据库现场容易遭受破坏 测试方法对数据库的更改操作会持久化到数据库中。 ...
-- by lsk -
直接使用Junit测试Spring ...
hrtc 写道有没有具体实例代码,支不支持增量测试,下面是我用junit的测试, ...
-- by xmx0632







评论排行榜