`

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--企业应用开发详解》
分享到:
评论
24 楼 01071405 2008-04-02  
记得以前进行java培训时候,学的就是dao,可惜工作了也就不用这个了,现在用spring,感觉以前培训的时候好幼稚
23 楼 MrLee23 2008-03-24  
tianhen 写道
我们一般是写了一个分页查询的方法:
  List pagedQuery(Page page, Where where);
where条件是一个条件树,可以任意进行组合完成复杂的条件查询。

你这种用Where的写法,会不会在安全上有问题呢,这样的话,任何一个程序员都可以操作这个表了,一下子就把数据层暴露了,虽然是select,但个人觉得为了安全,应该一个条件写一个方法~
22 楼 realorg 2008-03-23  
tianhen 写道

Where where = Where.rootWhere("prop",Where.EQ,object);


多条件查询怎么搞啊? 及有 and、or
21 楼 hajunma 2008-01-31  
通过Annotation的方法确实有点创意,不错啊。
20 楼 sswh 2008-01-30  
我目前的写法,只是感到不够灵活,到不够用的时候再想办法重构好了。。

@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。。。
}


19 楼 sswh 2008-01-30  
我目前的写法,只是感到不够灵活,到不够用的时候再想办法重构好了。。

@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。。。
}


18 楼 泡泡 2008-01-28  
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
17 楼 Clayz 2008-01-27  
我一般都是用的最后一种,用Criteria感觉和自己手工写没有多大区别。
只是想问问这个Where是如何设计的,不知可否讲解一下呢。
16 楼 spiritfrog 2008-01-26  
stamen 写道
  

    &nbsp;&nbsp;&nbsp;&nbsp;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
至于参数哪种好,我觉得除了那个参数一一写出来的,其他都还好
15 楼 hajunma 2008-01-21  
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)

  翻页查询我一般直接用JDBC,这样更灵活更高效,一般只对单条记录操作使用Hibernate,在实际应用中我发觉这种方式挺适用的。
14 楼 b051 2008-01-16  
要说查询条件的对象化, 那肯定是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)
13 楼 whilew 2008-01-15  
暂时来看大家用4的可能性比较大,但是此种方案对于动态灵活性可能有待提高,此种方式对于代码中重复性代码会较多
12 楼 stamen 2008-01-12  
joyfun 写道
ibatis用的就是方法4  查询条件当对象传入 这样挺好的

这种方式主要是要写很多个条件参数的对象,我倒觉得这种方式是不太好的。
11 楼 lxrw 2008-01-11  
恩 学习了.
10 楼 joyfun 2008-01-10  
ibatis用的就是方法4  查询条件当对象传入 这样挺好的
9 楼 icream 2008-01-10  
我觉得上面的5种方式都不够优雅
不过也没发现有什么好的方式~~~
除非自己实现一套类似与hibernate的Criteria
8 楼 icream 2008-01-10  
EXvision 写道
stamen 写道
tianhen 写道

Where where = Where.rootWhere("prop",Where.EQ,object);

  这种方式挺有创意的,也相当灵活,相当不错!


Hibernate似乎有类似的组件吧。。。当然了不用Hibernate就是另外一码事。。。

他的灵感来源于hibernate的Criteria
7 楼 rasonyang 2008-01-08  
Criteria更好.
and(
    eq("thisDN",750),
    gt("size",5)
    or(
      eq("otherDN",110),
      like("name","%Rason%")
    )
)
6 楼 EXvision 2008-01-08  
stamen 写道
tianhen 写道

Where where = Where.rootWhere("prop",Where.EQ,object);

  这种方式挺有创意的,也相当灵活,相当不错!


Hibernate似乎有类似的组件吧。。。当然了不用Hibernate就是另外一码事。。。
5 楼 e3002 2008-01-04  
没明白啊,能否给个例子?

相关推荐

    javaSE-day11-day12--主要实现了购物系统的dao层和service层

    在Java开发领域,一个完整的应用通常分为多个层次,如表现层、业务逻辑层和服务层,以及数据访问层等。...通过这样的练习,开发者能够更好地理解和掌握Java SE在实际项目中的应用,提升其软件开发能力。

    mybatis持久化dao生成工具

    在实际项目中,"mybatis持久化dao生成工具"结合版本控制系统如Git,能够更好地追踪代码变更,便于团队协作。同时,结合持续集成/持续部署(CI/CD)流程,可以确保代码生成的质量和一致性。总之,这样的工具对于大型...

    java-生成单表完全动态查询

    在Java开发中,数据库操作是不可或缺的一部分,而编写与之对应的实体类、DAO(Data Access Object)层以及各种查询方法通常是一项耗时的工作。"java-生成单表完全动态查询"这一技术就是为了简化这一过程,它能自动...

    java自动生成bean 根据数据库表及字段(.net 2.0版本)新添Dao层jdbc生成

    在Java开发中,数据访问对象(DAO)层是应用程序与数据库交互的重要部分。它负责将业务逻辑与数据存储分离,提供了一种抽象层,使得代码更易于维护和...通过理解这一过程,开发者可以更好地管理和优化他们的Java应用。

    Struts+Spring+Hibernate框架技术在Web开发中的研究与应用

    Struts的核心是MVC设计模式的应用,能够帮助开发者更好地组织代码结构,分离业务逻辑、展示逻辑与控制逻辑。 - **Spring**: Spring框架是一个开源的轻量级控制反转(IoC)和面向切面编程(AOP)容器。Spring提供了一种...

    Java面试资料之MyBatis相关

    MyBatis 是一个流行的 Java 框架,用于简化数据库操作。它是一个半 ORM(对象关系映射)框架,其主要功能是将 Java 对象映射到数据库记录...每个 Dao 方法对应一个唯一的 MappedStatement,所以方法签名必须是唯一的。

    其实spring data jpa比mybatis更好用.zip_JPA mybatis

    1. **易用性**:Spring Data JPA通过方法签名的查询方式降低了学习曲线,而MyBatis需要编写SQL和映射文件。 2. **灵活性**:MyBatis在SQL定制方面更自由,适合处理复杂查询;Spring Data JPA则更适合简单的CRUD操作...

    接口操作数据库的简单实现

    1. **接口的概念**:接口是一种抽象类型,它包含一组方法签名,但不提供具体的实现。在Java或C#等面向对象的语言中,接口用于定义对象必须实现的方法。这种方式可以确保不同的类遵循统一的规范,从而实现更好的协作...

    mysql-jdbc-Spring源代码分析

    - **探索Spring的异常处理机制**:Spring框架为处理各种常见的数据库异常提供了一套完整的机制,理解这些机制有助于更好地处理应用中的错误情况。 - **研究Spring的配置与注入机制**:理解如何通过Spring的依赖注入...

    SpringData数据层全栈方案高级应用Java代码.zip

    SpringData是Spring框架的一个重要模块,它为Java开发者提供了便捷的数据访问层的实现,涵盖了...通过深入研究和实践这些代码,开发者可以更好地掌握SpringData在实际项目中的应用,提升数据访问层的设计和实现能力。

    利用freemarker根据数据库字段自动生成form表单代码

    虽然上述过程可以通过手动编写代码实现,但在实际开发中,我们通常会借助自动化工具,如Apache的Maven插件或Gradle插件,它们能更好地集成到现有项目构建流程中。例如,使用`maven-compiler-plugin`配合Freemarker,...

    myBatis映射数据库表

    MyBatis是一个优秀的Java持久层框架,它支持定制化SQL、存储过程以及高级映射。在MyBatis中,映射数据库表是实现数据访问的关键步骤...理解并熟练掌握这些概念和机制,将有助于你更好地利用MyBatis进行数据访问和管理。

    1000道 互联网Java工程师面试题 485页_PDF密码解除.pdf

    - **性能:**一般来说,MyBatis比Hibernate的性能更好,因为它允许进行更精细的SQL优化。 - **学习曲线:**Hibernate的学习曲线比MyBatis要陡峭,因为Hibernate更加强调抽象和封装。 **6. #{}和${}的区别是什么?**...

    1000道 互联网Java工程师面试题 485页

    - **方法重载**:在MyBatis中不支持方法重载,因为调用接口方法时使用的是全限定类名+方法名作为key,无法区分相同的方法签名。 #### 10、Mybatis是如何进行分页的?分页插件的原理是什么? - **分页方式**:可以...

    spring data jpa 入门例子

    Spring Data JPA是Spring框架的一个模块,用于简化Java Persistence API(JPA)的使用,它提供了数据访问的抽象层,让开发者能够...记得不断探索和学习Spring Data JPA的更多高级功能,以便在实际项目中更好地利用它。

    1000道 互联网Java工程师面试题 485页-PDF密码解除

    - **学习曲线**:Hibernate的学习曲线相对平缓,而MyBatis需要一定的SQL基础才能更好地利用其特性。 #### 二、MyBatis核心概念 1. **#{}和${}的区别:** - **#{}**是预编译处理,可以防止SQL注入攻击;而**${}**...

    Room应用与源码学习

    综上所述,Room为Android开发者提供了便利且强大的数据库操作工具,通过理解其基本概念和源码,能更好地在项目中运用,提升开发效率和代码质量。在实际开发中,结合Room与其他Jetpack组件,如LiveData和ViewModel,...

    09_Mybatis.rar

    MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。...通过不断学习和实践,开发者可以更好地利用MyBatis解决各种数据访问问题。

    IsoBuster-v2.7 含有注册码

    更好的错误处理和几个重,机制来帮助获取数据无论如何你。 ?在通用和其他方法获得的数据,得到最好的利用您的CD / DVD - ROM驱动器的使用。 ?在小学和中学使用的文件系统获得的数据和/或使用的文件系统可能被忽略或'...

Global site tag (gtag.js) - Google Analytics