目前我们已经完成了CRM客户管理系统中的客户和联系人两个模块的内容了,其实我们会发现其中有很多的代码是重复的。尤其是DAO中的代码,因为DAO中都是些基本的CRUD的操作,但是可能只有其中个别的方法不同,基本的CRUD的方法的写法都是一样的,只是传入的对象不一样。举个例子,我们要完成保存客户和保存联系人,使用的方法其实都是save方法,只是一个传入的是Customer对象,而另一个则传入的是LinkMan对象。
- 客户的DAO的实现类:
- 联系人的DAO的实现类:
那么我们能不能通过抽取一个通用的工具类,完成简化DAO写法的操作呢?
抽取通用的增删改的操作
刚才已经提到了就是在整个的DAO的编码的过程中,我们可能做基本的一些CRUD的操作的时候,代码都是一样的,只是方法传入的类型不一样。我们可以想想用什么技术可以解决类型不一样的问题呢?在Java的开发中,我们可以使用泛型统一表述类型不一致的问题,泛型其实就是任意的类型。因为我们所需要的基本的一些方法的代码都是一样的,那么我们可以将这些每个DAO中都有的方法提取出来,抽取成一个通用的DAO的接口。
package com.meimeixia.crm.dao;
/**
* 通用的dao的接口
* @author liayun
*
*/
public interface BaseDao<T> {
public void save(T t);
public void update(T t);
public void delete(T t);
}
从以上通用的BaseDao<T>
接口的内容可以看出,我们现在只抽取了通用的增删改的操作,主要是因为这些操作抽取起来很简单,而查询操作抽取起来异常艰难,所以,我们先易后难,稍后再来抽取查询操作。我们设计出了上面这样一个通用的BaseDao<T>
接口,该泛型接口提供了最基本的增删改方法,使用泛型接口的原因是因为我们不知道要增删改的具体是一个什么类型的对象,当你传递进来的T是Customer,那就增删改Customer对象;你传递进来的T是LinkMan,那就增删改LinkMan对象。
然后,我们就要编写该泛型接口的具体的一个实现类了。因为我们在编写增加、删除、修改等方法的代码时比较容易,所以实现类可以写成下面的这个样子。
package com.meimeixia.crm.dao.impl;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import com.meimeixia.crm.dao.BaseDao;
/**
* 通用的dao的实现类
* @author liayun
*
* @param <T>
*/
public class BaseDaoImpl<T> extends HibernateDaoSupport implements BaseDao<T> {
@Override
public void save(T t) {
this.getHibernateTemplate().save(t);
}
@Override
public void update(T t) {
this.getHibernateTemplate().update(t);
}
@Override
public void delete(T t) {
this.getHibernateTemplate().delete(t);
}
}
至此,通用的增删改的操作就算是抽取完成了。接着,我们就要修改dao层中XxxDao接口以及与其相对应的XxxDaoImpl实现类的代码了。
-
CustomerDao接口以及与其相对应的CustomerDaoImpl实现类的代码该如何修改呢?先修改CustomerDao接口,让其继承通用的
BaseDao<T>
接口。package com.meimeixia.crm.dao; import java.util.List; import org.hibernate.criterion.DetachedCriteria; import com.meimeixia.crm.domain.Customer; /** * 客户管理的dao的接口 * @author liayun * */ public interface CustomerDao extends BaseDao<Customer> { Integer findCount(DetachedCriteria detachedCriteria); List<Customer> findByPage(DetachedCriteria detachedCriteria, Integer begin, Integer pageSize); Customer findById(Long cust_id); List<Customer> findAll(); }
再修改与其相对应的CustomerDaoImpl实现类,让其继承通用的
BaseDaoImpl<T>
实现类。package com.meimeixia.crm.dao.impl; import java.util.List; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Projections; import org.springframework.orm.hibernate5.support.HibernateDaoSupport; import com.meimeixia.crm.dao.CustomerDao; import com.meimeixia.crm.domain.Customer; /** * 客户管理的dao的实现类 * @author liayun * */ public class CustomerDaoImpl extends BaseDaoImpl<Customer> implements CustomerDao { //dao中带条件去统计个数的方法 @Override public Integer findCount(DetachedCriteria detachedCriteria) { //还得给DetachedCriteria对象设置条件,你要没设置条件,就查询所有了。 //因为我们现在要发送类似select count(*) from xxx where [条件]这样的sql语句 detachedCriteria.setProjection(Projections.rowCount()); List<Long> list = (List<Long>) this.getHibernateTemplate().findByCriteria(detachedCriteria); if (list.size() > 0) { return list.get(0).intValue(); } return null; } //dao中分页查询客户的方法 @Override public List<Customer> findByPage(DetachedCriteria detachedCriteria, Integer begin, Integer pageSize) { //在查询之前,先要把DetachedCriteria对象中的count那个地方给清空掉,去掉之后,默认就是查所有 detachedCriteria.setProjection(null); return (List<Customer>) this.getHibernateTemplate().findByCriteria(detachedCriteria, begin, pageSize); } //dao中根据id去查询客户的方法 @Override public Customer findById(Long cust_id) { return this.getHibernateTemplate().get(Customer.class, cust_id); } //dao中查询所有客户的方法 @Override public List<Customer> findAll() { return (List<Customer>) this.getHibernateTemplate().find("from Customer"); } }
-
LinkManDao接口以及与其相对应的LinkManDaoImpl实现类的代码又该如何修改呢?道理同上,先修改LinkManDao接口,让其继承通用的
BaseDao<T>
接口。package com.meimeixia.crm.dao; import java.util.List; import org.hibernate.criterion.DetachedCriteria; import com.meimeixia.crm.domain.LinkMan; /** * 联系人的dao的接口 * @author liayun * */ public interface LinkManDao extends BaseDao<LinkMan> { Integer findCount(DetachedCriteria detachedCriteria); List<LinkMan> findByPage(DetachedCriteria detachedCriteria, Integer begin, Integer pageSize); LinkMan findById(Long lkm_id); }
再修改与其相对应的LinkManDaoImpl实现类,让其继承通用的
BaseDaoImpl<T>
实现类。package com.meimeixia.crm.dao.impl; import java.util.List; import org.hibernate.criterion.DetachedCriteria; import org.hibernate.criterion.Projections; import com.meimeixia.crm.dao.LinkManDao; import com.meimeixia.crm.domain.LinkMan; /** * 联系人的dao的实现类 * @author liayun * */ public class LinkManDaoImpl extends BaseDaoImpl<LinkMan> implements LinkManDao { //dao中统计个数的方法 @Override public Integer findCount(DetachedCriteria detachedCriteria) { detachedCriteria.setProjection(Projections.rowCount()); List<Long> list = (List<Long>) this.getHibernateTemplate().findByCriteria(detachedCriteria); if (list.size() > 0) { return list.get(0).intValue(); } return null; } //dao中分页查询联系人的方法 @Override public List<LinkMan> findByPage(DetachedCriteria detachedCriteria, Integer begin, Integer pageSize) { //首先要把count(*)语句给清空掉 detachedCriteria.setProjection(null); List<LinkMan> list = (List<LinkMan>) this.getHibernateTemplate().findByCriteria(detachedCriteria, begin, pageSize); return list; } //dao中根据id去查询联系人的方法 @Override public LinkMan findById(Long lkm_id) { return this.getHibernateTemplate().get(LinkMan.class, lkm_id); } }
-
UserDao接口以及与其相对应的UserDaoImpl实现类的代码又该如何修改呢?道理同上,先修改UserDao接口,让其继承通用的
BaseDao<T>
接口。温馨提示:通用的BaseDao<T>
接口里面只是定义了一些通用的增删改查的方法声明,特有的方法声明必须定义在子类接口中。package com.meimeixia.crm.dao; import com.meimeixia.crm.domain.User; /** * 用户管理的dao的接口 * @author liayun * */ public interface UserDao extends BaseDao<User> { User login(User user);//登录方法是用户所特有的,所以得留着 }
再修改与其相对应的UserDaoImpl实现类,让其继承通用的
BaseDaoImpl<T>
实现类。package com.meimeixia.crm.dao.impl; import java.util.List; import com.meimeixia.crm.dao.UserDao; import com.meimeixia.crm.domain.User; /** * 用户管理的dao的实现类 * @author liayun * */ public class UserDaoImpl extends BaseDaoImpl<User> implements UserDao { //dao中根据用户名和密码进行查询的方法 @Override public User login(User user) { List<User> list = (List<User>) this.getHibernateTemplate().find("from User where user_code = ? and user_password = ?", user.getUser_code(), user.getUser_password()); //判断一下 if (list.size() > 0) { return list.get(0); } return null; } }
-
BaseDictDao接口以及与其相对应的BaseDictDaoImpl实现类的代码又该如何修改呢?道理同上,先修改BaseDictDao接口,让其继承通用的
BaseDao<T>
接口。温馨提示:通用的BaseDao<T>
接口里面只是定义了一些通用的增删改查的方法声明,特有的方法声明必须定义在子类接口中。package com.meimeixia.crm.dao; import java.util.List; import com.meimeixia.crm.domain.BaseDict; /** * 数据字典dao的接口 * @author liayun * */ public interface BaseDictDao extends BaseDao<BaseDict> { List<BaseDict> fingByTypeCode(String dict_type_code); }
再修改与其相对应的BaseDictDaoImpl实现类,让其继承通用的
BaseDaoImpl<T>
实现类。package com.meimeixia.crm.dao.impl; import java.util.List; import com.meimeixia.crm.dao.BaseDictDao; import com.meimeixia.crm.domain.BaseDict; /** * 数据字典dao的实现类 * @author liayun * */ public class BaseDictDaoImpl extends BaseDaoImpl<BaseDict> implements BaseDictDao { //根据类型编码去查询字典数据 @Override public List<BaseDict> fingByTypeCode(String dict_type_code) { return (List<BaseDict>) this.getHibernateTemplate().find("from BaseDict where dict_type_code = ?", dict_type_code); } }
dao层改造完毕之后,大家不妨可以做个测试,我就不再这里测试了(偷懒了),但我相信是没有任何问题的!
抽取通用的查询的操作
大家现在考虑一个问题,就是在抽取通用的查询方法的时候,到底该如何抽取呢?就拿根据ID查询一个对象的方法来说,我们应该怎样对它进行抽取呢?首先,在通用的BaseDao<T>
接口中定义了一个根据ID查询一个对象的方法。
package com.meimeixia.crm.dao;
import java.io.Serializable;
/**
* 通用的dao的接口
* @author liayun
*
*/
public interface BaseDao<T> {
public void save(T t);
public void update(T t);
public void delete(T t);
//查询一个
public T findById(Serializable id);
}
然后,在通用的BaseDaoImpl<T>
实现类中实现以上定义的方法。
使用Hibernate模板类的get方法根据ID从数据库表里面进行查询时,由于它不知道要把这些数据封装到哪儿去,所以应该传递给它一个对象的Class类型的参数。如果像上面那样写肯定是错误的,因为T这个泛型是没有class属性的,T只有在真正使用的时候才会有具体的类型。那么我们要如何解决这个问题呢?一共有两种解决方案,下面我会详细地介绍它们。
解决方案一:在通用实现类的构造方法中传入一个Class
这里,我先来讲第一种解决方案。既然我们通用的DAO中需要一个具体类型的Class对象,那么我们能不能从外部给其传入一个需要的类型的Class呢?其实这样是可以的,要想从外部传入一个值,那么我们可以在DAO中提供一个成员的属性,并在构造这个类的时候,将这个需要的属性设置进来。如此一来,通用的BaseDaoImpl<T>
实现类就要修改成下面这个样子了。
package com.meimeixia.crm.dao.impl;
import java.io.Serializable;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import com.meimeixia.crm.dao.BaseDao;
/**
* 通用的dao的实现类
* @author liayun
*
* @param <T>
*/
public class BaseDaoImpl<T> extends HibernateDaoSupport implements BaseDao<T> {
private Class clazz;//定义一个成员变量
//提供一个构造方法,在构造方法中传入一个具体类型的Class
public BaseDaoImpl(Class clazz) {
this.clazz = clazz;
}
@Override
public void save(T t) {
this.getHibernateTemplate().save(t);
}
@Override
public void update(T t) {
this.getHibernateTemplate().update(t);
}
@Override
public void delete(T t) {
this.getHibernateTemplate().delete(t);
}
@Override
public T findById(Serializable id) {
return (T) this.getHibernateTemplate().get(clazz, id);
}
}
那么这个时候通用的BaseDaoImpl<T>
实现类中的根据ID查询某个对象的方法就不会出现问题了。接下来,我们就要修改dao层中XxxDao接口以及与其相对应的XxxDaoImpl实现类的代码了。下面,我会举一个例子,来告诉大家如何修改CustomerDao接口以及与其相对应的CustomerDaoImpl实现类的代码。
- 先修改CustomerDao接口,因为父接口中已经定义了根据ID查询某个对象的方法,所以子接口中可以直接从父接口中继承过来,而不再需要自己编写了。
- 再修改与其相对应的CustomerDaoImpl实现类。
既然现在我们都知道该如何抽取根据ID查询一个对象的方法了,那其他查询的方法抽取起来还不是手到擒来。此时,通用的BaseDao<T>
接口就要修改成下面这个样子了。
package com.meimeixia.crm.dao;
import java.io.Serializable;
import java.util.List;
import org.hibernate.criterion.DetachedCriteria;
/**
* 通用的dao的接口
* @author liayun
*
*/
public interface BaseDao<T> {
public void save(T t);
public void update(T t);
public void delete(T t);
//查询一个
public T findById(Serializable id);
//查询所有
public List<T> findAll();
//统计个数
public Integer findCount(DetachedCriteria detachedCriteria);
//分页查询的方法
public List<T> findByPage(DetachedCriteria detachedCriteria, Integer begin, Integer pageSize);
}
相应地,通用的BaseDaoImpl<T>
实现类就要修改成下面这个样子了。
package com.meimeixia.crm.dao.impl;
import java.io.Serializable;
import java.util.List;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import com.meimeixia.crm.dao.BaseDao;
/**
* 通用的dao的实现类
* @author liayun
*
* @param <T>
*/
public class BaseDaoImpl<T> extends HibernateDaoSupport implements BaseDao<T> {
private Class clazz;//定义一个成员变量
//提供一个构造方法,在构造方法中传入一个具体类型的Class
public BaseDaoImpl(Class clazz) {
this.clazz = clazz;
}
@Override
public void save(T t) {
this.getHibernateTemplate().save(t);
}
@Override
public void update(T t) {
this.getHibernateTemplate().update(t);
}
@Override
public void delete(T t) {
this.getHibernateTemplate().delete(t);
}
@Override
public T findById(Serializable id) {
return (T) this.getHibernateTemplate().get(clazz, id);
}
//查询所有的方法
@Override
public List<T> findAll() {
// return this.getHibernateTemplate().find("from com.meimeixia.crm.domain.Customer");
return (List<T>) this.getHibernateTemplate().find("from " + clazz.getSimpleName());//clazz.getSimpleName()可以直接得到具体的类的全名称
}
//统计个数的方法
@Override
public Integer findCount(DetachedCriteria detachedCriteria) {
//设置统计个数的条件
detachedCriteria.setProjection(Projections.rowCount());
List<Long> list = (List<Long>) this.getHibernateTemplate().findByCriteria(detachedCriteria);
if (list.size() > 0) {
return list.get(0).intValue();
}
return null;
}
//分页查询的方法
@Override
public List<T> findByPage(DetachedCriteria detachedCriteria, Integer begin, Integer pageSize) {
detachedCriteria.setProjection(null);
return (List<T>) this.getHibernateTemplate().findByCriteria(detachedCriteria, begin, pageSize);
}
}
使用这种解决方案完成了通用的查询操作的抽取之后,那么我们在编写dao层时便会变得非常简单。
- 此时,CustomerDao接口以及与其相对应的CustomerDaoImpl实现类的代码该如何修改呢?先修改CustomerDao接口,因为父接口中已经定义了基本的CRUD的操作的相关方法,所以子接口中可以直接从父接口中继承过来,而不再需要自己编写了。
再修改与其相对应的CustomerDaoImpl实现类,在该实现类中,也不再需要那些任何有关CRUD的代码了,因为在通用的BaseDaoImpl<T>
实现类中已经对这些方法实现过了,我们自己编写的CustomerDaoImpl实现类只需要继承该通用实现类就可以了。但是父类中没有无参数的构造,所以子类需要提供一个构造方法,调用父类的有参数的构造,这样我们就可以将客户的Class对象传递到父类中了。
- LinkManDao接口以及与其相对应的LinkManDaoImpl实现类的代码又该如何修改呢?道理同上,先修改LinkManDao接口。
再修改与其相对应的LinkManDaoImpl实现类。
- UserDao接口以及与其相对应的UserDaoImpl实现类的代码又该如何修改呢?道理同上,先修改UserDao接口。
再修改与其相对应的UserDaoImpl实现类。
- BaseDictDao接口以及与其相对应的BaseDictDaoImpl实现类的代码又该如何修改呢?道理同上,先修改BaseDictDao接口。
再修改与其相对应的BaseDictDaoImpl实现类。
dao层再次改造完毕之后,大家不妨可以做个测试,我就不再这里测试了(偷懒了),但我相信是没有任何问题的!这样,我们就编写了一个通用的DAO了,以后我们在需要写DAO的代码的时候,DAO中任何代码都不用写了,只需要继承通用的BaseDaoImpl<T>
实现类,提供一个构造方法就可以了。
有很大部分人感觉这样已经很好了,其实我们的通用DAO还可以改进,现在我们需要在DAO中提供构造方法,我们能不能连构造方法都不提供了呢?这是可以的,那就是在DAO中直接继承通用的BaseDaoImpl<T>
实现类就可以了,也就是下面要讲到的第二种解决方案。
解决方案二:通过泛型的反射抽取通用的DAO
要实现不提供构造方法,直接继承通用的BaseDaoImpl<T>
实现类,其实不是不可以,只要父类中不提供有参数的构造方法就可以了。但是父类中的有参数的构造方法是为了在这个类中要获得到具体类的Class对象,只要能在父类中获得需要的Class,而不提供一个有参的构造就可以了。那么如何去解决这个问题呢?
要想解决这个问题就需要了解泛型的反射了。也就是说我们在进行查询操作的时候,要获得的是具体类型的Class。
但是我们现在使用的是泛型T,其实如果能够获得到泛型所代表的具体的类型的Class就可以了,也就是说我们能够获得到参数化类型中的实际类型参数即可。
接下来,我们需要做的事情就是在父类的构造方法中获得子类继承父类时的参数化类型中的实际类型参数。查阅JDK的API帮助文档可知,在Class类中的有如下两个重要方法:
我们可以通过以上两个方法来反射泛型,获得到子类继承父类时的参数化类型中的实际类型参数。如此一来,通用的BaseDaoImpl<T>
实现类的代码就要修改为下面这个样子了。
package com.meimeixia.crm.dao.impl;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.springframework.orm.hibernate5.support.HibernateDaoSupport;
import com.meimeixia.crm.dao.BaseDao;
/**
* 通用的dao的实现类
* @author liayun
*
* @param <T>
*/
public class BaseDaoImpl<T> extends HibernateDaoSupport implements BaseDao<T> {
private Class clazz;//定义一个成员变量
/*
* 如果不想子类上有构造方法,那就必须在父类中提供无参构造,而且要在无参构造中获得具体类型的Class,
* 其实,这个具体类型的Class就是参数化类型(ParameterizedType)中的实际类型参数。
*
*/
public BaseDaoImpl() {
//进行反射,第一步就是获得Class(即CustomerDaoImpl.class)
Class clazz = this.getClass();//this.getClass()获得的是正在被调用的那个类的Class,有可能是CustomerDaoImpl类的Class,也有可能是LinkManImpl类的Class
//查看JDK的API
Type type = clazz.getGenericSuperclass();//就是参数化的类型,也即ParameterizedType,获得的有可能是BaseDaoImpl<Customer>,也有可能是BaseDaoImpl<LinkMan>
System.out.println(type);//可以看到Eclipse控制台打印了com.meimeixia.crm.dao.impl.BaseDaoImpl<com.meimeixia.crm.domain.Customer>类似这样的内容
//所得到的Type就是一个参数化的类型,所以可以将Type强转成参数化的类型
ParameterizedType pType = (ParameterizedType) type;
//通过参数化类型来获得实际类型参数,为什么会得到一个实际类型参数的数组(即Type[])呢?别忘了,还有这种结构:Map<String, Integer>
Type[] types = pType.getActualTypeArguments();
//只获得其中的第一个实际类型参数
this.clazz = (Class) types[0];//可能得到Customer.class,也有可能是LinkMan.class,也有可能是User.class
}
@Override
public void save(T t) {
this.getHibernateTemplate().save(t);
}
@Override
public void update(T t) {
this.getHibernateTemplate().update(t);
}
@Override
public void delete(T t) {
this.getHibernateTemplate().delete(t);
}
@Override
public T findById(Serializable id) {
return (T) this.getHibernateTemplate().get(clazz, id);
}
//查询所有的方法
@Override
public List<T> findAll() {
// return this.getHibernateTemplate().find("from com.meimeixia.crm.domain.Customer");
return (List<T>) this.getHibernateTemplate().find("from " + clazz.getSimpleName());//clazz.getSimpleName()可以直接得到具体的类的全名称
}
//统计个数的方法
@Override
public Integer findCount(DetachedCriteria detachedCriteria) {
//设置统计个数的条件
detachedCriteria.setProjection(Projections.rowCount());
List<Long> list = (List<Long>) this.getHibernateTemplate().findByCriteria(detachedCriteria);
if (list.size() > 0) {
return list.get(0).intValue();
}
return null;
}
//分页查询的方法
@Override
public List<T> findByPage(DetachedCriteria detachedCriteria, Integer begin, Integer pageSize) {
detachedCriteria.setProjection(null);
return (List<T>) this.getHibernateTemplate().findByCriteria(detachedCriteria, begin, pageSize);
}
}
在以上通用实现类的无参构造中,this究竟指代的是谁呢?这里,this并不是指代的是自己,而是到底是谁调用了BaseDaoImpl()这个构造方法,this就指向谁。修改好这样的通用实现类之后,就会有诸如CustomerDaoImpl等类去继承它,用比较专业的术语来说即子类继承父类,等一会儿new子类对象的时候,根据Java语言new对象的机制,在new子类对象的时候,子类会调用父类的无参构造,所以this指代的是子类对象,到底是哪个子类对象,这无从知晓。
这个时候,我们具体的DAO中就可以不用编写构造方法就可以实现相应的功能了。例如,CustomerDaoImpl实现类的代码就变成了下面这样。
温馨提示:如果DAO中都是些基本的CRUD的操作,那么其他的代码是不需要写的,但是如果一些方法是特有的,那么就需要将特有的方法写到各自的DAO中了。