首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

五种DAO查询方法的签名方式,哪个更好

2012-11-10 
5种DAO查询方法的签名方式,哪个更好?DAO类中查询方法的签名多种多样,大家都使用什么签名方法呢?拿出来讨论

5种DAO查询方法的签名方式,哪个更好?
   DAO类中查询方法的签名多种多样,大家都使用什么签名方法呢?拿出来讨论一下吧。  

        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)


&nbsp;&nbsp;&nbsp;&nbsp;OrderQueryParam查询条件对象封装了hql查询语句可能会用到的条件项参数,在查询方法内部,开发者必须判断查询条件对象的属性并正确绑定条件项参数。由于需要为条件项参数定义一个类,因此会造成类数目的膨胀,有时甚至一个实体DAO需要对应多个查询条件参数类。另外,当需要添加一个新的条件项参数时,条件封装类还需要进行相应调整。

  方式5:使用Map传递条件项参数

    &nbsp;&nbsp;&nbsp;&nbsp;另一种被广泛使用的方法是采用Map传递条件项参数,键为参数名,值为参数值:
List<Order> findOrder(String hql,Map params)

  &nbsp;&nbsp;&nbsp;&nbsp;使用这种方式,接口方法签名可以在条件项发生变化的情况下保持稳定,同时通过键指定条件项参数名在一定程度上解决了接口的健壮性(当调用者指定错误参数名时容易得到错误报警)。但和方法2、3一样调用者必须通过接口Javadoc才能明白不同条件项要以什么名称进行设置。

注:以上5个查询方法签名的总结摘自于《精通Spring 2.x--企业应用开发详解》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@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。。。}

21 楼 hajunma 2008-01-31   通过Annotation的方法确实有点创意,不错啊。 22 楼 realorg 2008-03-23   tianhen 写道
Where where = Where.rootWhere("prop",Where.EQ,object);

多条件查询怎么搞啊? 及有 and、or 23 楼 MrLee23 2008-03-24   tianhen 写道我们一般是写了一个分页查询的方法:
  List pagedQuery(Page page, Where where);
where条件是一个条件树,可以任意进行组合完成复杂的条件查询。
你这种用Where的写法,会不会在安全上有问题呢,这样的话,任何一个程序员都可以操作这个表了,一下子就把数据层暴露了,虽然是select,但个人觉得为了安全,应该一个条件写一个方法~ 24 楼 01071405 2008-04-02   记得以前进行java培训时候,学的就是dao,可惜工作了也就不用这个了,现在用spring,感觉以前培训的时候好幼稚

热点排行