现在的位置: 首页 > 综合 > 正文

基于Spring的DAO层设计

2018年02月12日 ⁄ 综合 ⁄ 共 6115字 ⁄ 字号 评论关闭
概述
    Spring为各种支持的ORM提供了统一基于模板模式的Template基类,此外还为使用模板类提供了方便的Support支持类,它内容包含了一个 Template,Spring推荐开发者直接继承这个Support类定义自己的DAO。但是,在实际应用中,直接继承Spring的Support定 义实体类DAO存在一些不足之处,我们将探讨通过引入一个基类简化子类的编码的思路。

    此外,Spring提供的各种ORM Template类的查询方法使用Object[]传递查询条件参数,很多开发者发现使用Object[]传递查询条件并非是一个最优的办法,因此出现了 很多演化的查询方法的设计方式,本文对此进行了汇总,大家可以通过比较选择自己认为最适合的方式。

    DAO基类的设计
    虽然Spring通过模板类和支持类为各种ORM框架提供了出色的支持,但也存在一些不足。首先,这些模板类大多都是不支持泛型的,对于保存、更改、删除 等操作,泛型的意义并不是很大,但对于加载实体,查询实体集合的操作,泛型可以带来很大的方便。其次,由于支持类通过类似于 getHibernateTemplate()、getSqlMapClientTemplate()的方式向子类开放模板类,子类在执行数据操作时,必 须采取诸如:getSqlMapClientTemplate().insert("addForum", forum)的形式,子类代码显得比较拖沓。
    在实际应用中,开发者一般会在Spring支持类的基础上编写自己的DAO其类,进行自己的封装,以获得泛型的支持并提供自己的代理方法使子类避免显式调 用模板类的代码。DAO基类并不需要对模板类的所有方法进行代理,仅对常用的方法(如CRUD操作方法)进行代理,而对不常用的方法则可要求子类显式调用 模板实例完成。这样,一方面简化常用方法的调用,另一方面又使基类不过于复杂。 
       Spring为各种支持的ORM提供了统一基于模板模式的Template基类,此外还为使用模板类提供了方便的Support支持类,它内容包含了一个 Template,Spring推荐开发者直接继承这个Support类定义自己的DAO。但是,在实际应用中,直接继承Spring的Support定 义实体类DAO存在一些不足之处,我们将探讨通过引入一个基类简化子类的编码的思路。     此外,Spring提供的各种ORM Template类的查询方法使用Object[]传递查询条件参数,很多开发者发现使用Object[]传递查询条件并非是一个最优的办法,因此出现了 很多演化的查询方法的设计方式,本文对此进行了汇总,大家可以通过比较选择自己认为最适合的方式。     虽然Spring通过模板类和支持类为各种ORM框架提供了出色的支持,但也存在一些不足。首先,这些模板类大多都是不支持泛型的,对于保存、更改、删除 等操作,泛型的意义并不是很大,但对于加载实体,查询实体集合的操作,泛型可以带来很大的方便。其次,由于支持类通过类似于 getHibernateTemplate()、getSqlMapClientTemplate()的方式向子类开放模板类,子类在执行数据操作时,必 须采取诸如:getSqlMapClientTemplate().insert("addForum", forum)的形式,子类代码显得比较拖沓。     在实际应用中,开发者一般会在Spring支持类的基础上编写自己的DAO其类,进行自己的封装,以获得泛型的支持并提供自己的代理方法使子类避免显式调 用模板类的代码。DAO基类并不需要对模板类的所有方法进行代理,仅对常用的方法(如CRUD操作方法)进行代理,而对不常用的方法则可要求子类显式调用 模板实例完成。这样,一方面简化常用方法的调用,另一方面又使基类不过于复杂。     由于一般的实体类对应的DAO都必须拥有CRUD操作,与其在每个实体DAO接口中复杂定义这些方法,不如提供一个通用的DAO接口。具体的实体DAO接 口可以扩展这个通用DAO接口并定义实体类相关的其它操作方法就可以了。以Hibernate为例,图 2中给出了典型的DAO类的结构:


图 2 DAO层类设计

BaseDao<T>通用基础接口对所有实体DAO接口的通用方法进行了抽象并提供泛型的支持,其代码如代码清单 12所示:
代码清单 12 BaseDao<T>:通用接口
package com.baobaotao.dao;
import java.io.Serializable;
import java.util.List;
public interface BaseDao<T> {
T get(Serializable id); ①通过泛型的支持,加载实体的方法仅需要一个主键就可以了
List<T> getAll(); ②直接返回强类型的集合类

③泛型并没有给保存、删除和更改的操作带来方便,因此这些方法可以不使用泛型
void save(Object o);
void remove(Object o);
void update(Object o);

}

   在没有泛型支持的情况下查询实体,不但要指定主键值还需要指定实体类型,不但方法入参比较复杂,调用者还需要对返回的结果对象进行强制类型转换,这两种缺点都可以通过在基类中引入泛型得到彻底的克服。
代码清单 13 HibernateBaseDao<T>:DAO基类

package com.baobaotao.dao;
mport java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
public class HibernateBaseDao<T> extends HibernateDaoSupport implements BaseDao<T>{
private Class<T> entityClass; ①实体类的类型
public HibernateBaseDao() {②通过反射机制获取泛型对应的实体类的类型
Type genType
= getClass().getGenericSuperclass();
Type[]
params = ((ParameterizedType) genType).getActualTypeArguments();
entityClass
= (Class)params[0];
}

    ③-1通过代理模板类、泛型反射获取实体类的类型的机制得到了优雅的接口

public T get(Serializable id) {
return (T)getHibernateTemplate().load(entityClass, id);
}
public List<T> getAll() {③-2 return getHibernateTemplate().loadAll(entityClass);
}
public void save(Object o) {
getHibernateTemplate().saveOrUpdate(o);
}
public void remove(Object o) {
getHibernateTemplate().delete(o);
}
public void update(Object o) {
getHibernateTemplate().update(o);
}

}

    基类HibernateBaseDao<T>扩展于HibernateDaoSupport并实现了通用接口。此外,还可以在该类中提供更多 方便的操作方法化解子类的实现难度。这样,子类仅需要指定泛型对应的实体类就拥有了各种通用的数据操作能力。如果实体DAO的数据操作仅是一些常见的 CRUD等操作,子类甚至可以不编写一行代码,实体DAO编码生产率将提到极大的提高,抽象性上升了一个新的台阶。通过扩展 HibernateBaseDao<T>我们可以得到一个被极大简化的ForumHibernateDao版本:

package com.baobaotao.dao.hibernate;

    ①通过扩展泛型的DAO基类自动拥有常见功能的操作

public class ForumHibernateDao extends HibernateBaseDao<Forum> implements ForumDao{
public long getForumNum() {②非常见的数据访问方法
Iterator iter
= getHibernateTemplate().iterate(
"select count(f.forumId) from Forum f");
return ((Long)iter.next());
}
}

    通过扩展HibernateBaseDao<T>基类并借由泛型指定实体类,ForumHibernateDao立即拥有了对Forum实例 进行CRUD操作的功能,并且这些功能都是泛型版本的:如当你调用ForumHibernateDao#get(Serializable id)时,将直接返回Forum类型的实体对象。因此在ForumHibernateDao类中你仅需要对那些非通用的数据操作提供自定义方法就大功告成 了。按照相似的方式,我们还可以轻松完成对TopicHibernateDao、PostHibernateDao的改造,你将发现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)),所以方法内部的处理相对还是比较简单的。开发者采用如下的方式进行调用:

Object[] params = new Object[]{"112",new Integer(12),new Integer(1)};
list
= findOrder(hql,params);

    方式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需要对应多个查询条件封装类。另外,当需要添加一个新的条 件项参数时,条件封装类还需要进行相应调整。

OrderQueryParam param = new OrderQueryParam();
param.setUserName(
"zhang");
param.setDeptId(
12);
param.setStatus()
0;
list
= findOrder(hql,param);

    方式5:使用Map传递条件项参数
    另一种被广泛使用的方法是采用Map传递条件项参数,键为参数名,值为参数值:
    List<Order> findOrder(String hql,Map params)
    这种方式可以在条件项变化,接口方法依然可以保持稳定,同时通过键指定参数名的方式在一定程度上解决了接口的健壮性(当调用者指定错误参数名时容易得到错误报警)。但和方法2、3一样调用者必须通过接口Javadoc才能进行调用。

Map paramMap = new HashMap();
paramMap.put(
"userName","zhang");
paramMap.put(
"deptId",12);
paramMap.put(
"status",0);
list
= findOrder(hql,paramMap);

    小结
    虽然Spring为我们提供了使用各种ORM框架的方便类,但这离实际的应用开发还有一段需要缩短的距离。具体的应用一般会在Spring所提供的支持类 基础上提供过一步的封装,建立方便的DAO基类,简化接口方法添加泛型支持等功能。一个设计良好的DAO基类可以大大减少DAO层整体代码的总量,提高项 目的开发效率。

 

抱歉!评论已关闭.