泛型理解及应用(二):使用泛型编写通用型Dao层
相信目前所有的IT公司网站在设计WEB项目的时候都含有持久层,同样地使用过Hibernate的程序员都应该看过或者了解过Hibernate根据数据库反向生成持久层代码的模板。对于Hibernate生成的这个通用型的模板,可以看一下了解Hibernate是怎么处理这一层的。笔者来到公司作为开发的时候,项目已经有了一个比较成型的Dao层代码。当然这个层级代码是用C#+NHibernate写的。在这里刚好用了泛型把整个代码改成使用Java+Hibernate去实现。
首先,先大致阐述下整个通用Dao层是怎么设计的。
具体的类图如下:

大致的设计思路:
1. 工厂类:用来注册Dao层里面的每一个接口,通过HibernateFactory获取到每个具体Dao的实例;实现面向接口式编程。
2. 抽象层:使用泛型抽象出一个Dao层面的接口,使得每个具体的类操作都有自己的Dao实例进行操作。
3. 使用虚拟类对抽象层进行重写,没必要为每个Dao编写一次重复的代码。
对于这套模板,大部分的都是概念性的东西,说白了这个设计里面最重要的就是AbstractDao<T>这个类,因为所有的代码基本都囊括在这里面了。下面,来贴下这个类里面的代码。
package com.template.dao.interfaces; import java.io.Serializable;
import java.util.List; /**
* @Author: Travelsky_CLSUN
* @Date: Created on 17-5-18
* @Description: 抽象一个Dao层级内容,含有基本的增删改查功能
*/
public interface IDao<T>
{ void add(T t); void delete(Serializable id); void update(T t); T findById(Serializable id); List<T> findAll(); }
IDao定义
package com.template.dao.interfaces; import com.template.domain.Person; /**
* @Author: Travelsky_CLSUN
* @Date: Created on 17-5-18
* @Description: Dao工厂,用来注册每个不同的Dao
*/
public interface IDaoFactory
{
IPersonDao<? extends Person> getPersonDao();
}
IDao工厂
Dao层工厂的接口,抽象出每个不同的接口,在这里主要用来作为不同接口的注册。在使用其实现工厂HibernateFactory时,必须先在该工厂里面注册。
package com.template.dao.impl; import com.template.dao.interfaces.IDaoFactory;
import com.template.dao.interfaces.IPersonDao;
import com.template.domain.Person; public class HibernateFactory implements IDaoFactory
{
public static HibernateFactory factoryInstance = null; public static HibernateFactory getFactory()
{
if (factoryInstance == null)
{
factoryInstance = new HibernateFactory();
}
return factoryInstance;
} private HibernateFactory()
{ } @Override
public IPersonDao<Person> getPersonDao()
{
return new PersonDaoImpl();
}
}
单例的HiberanateFactory,用来获取每个接口的实现
HibernateFactory,主要是用来获取每个接口的实现,实现面向接口方式的编程。
package com.template.dao.impl; import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List; import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction; import com.template.dao.interfaces.IDao;
import com.template.utils.HibernateUtil; /**
* @Author: Travelsky_CLSUN
* @Date: Created on 17-5-18
* @Description: 抽象的Dao层,用于实现每个不同Dao上面的实现,避免重复编写代码
*/
public abstract class AbstractDao<T> implements IDao<T>
{
Type persistenType;
Class<? extends Object> persistenClass; // 定义事务
Transaction transaction = null;
Session session = null; public AbstractDao()
{
Type superClassType = this.getClass().getGenericSuperclass();
if (null != superClassType && superClassType instanceof ParameterizedType)
{
persistenType = ((ParameterizedType) superClassType).getActualTypeArguments()[];
persistenClass = (Class<?>) persistenType;
}
else
{
System.out.println("类:" + this.getClass().getName() + "尚未实现接口或者定义错误");
}
} @Override
public void add(T t)
{ // 获取session,让每个进来的都应该有一个连接
session = HibernateUtil.getSession();
transaction = session.beginTransaction();
try
{
// save把内容放进hibernate一级缓存
session.save(t);
// 没进行事务提交的话,hibernate不会把数据同步到数据库,即使是调用session.flush
transaction.commit();
} catch (Exception e)
{
e.printStackTrace();
transaction.rollback();
} finally
{
transaction = null;
session.close();
} } @Override
public void delete(Serializable id)
{
// 获取session,让每个进来的都应该有一个连接
session = HibernateUtil.getSession();
transaction = session.beginTransaction();
Object obj = session.get(persistenClass, id);
try
{
if (null != obj)
{
session.delete(obj);
transaction.commit();
}
} catch (Exception e)
{
transaction.rollback();
} finally
{
transaction = null;
session.close();
}
} @Override
public void update(T t)
{
// 获取session,让每个进来的都应该有一个连接
session = HibernateUtil.getSession();
session.save(t);
} @Override
public T findById(Serializable id)
{
// 获取session,让每个进来的都应该有一个连接
session = HibernateUtil.getSession();
try
{
Object obj = session.get(persistenClass, id);
return (T) obj;
} catch (Exception e)
{
return null;
} finally
{
session.close();
} } @Override
public List<T> findAll()
{
// 获取session,让每个进来的都应该有一个连接
session = HibernateUtil.getSession();
try
{
Query query = session.createQuery("from " + persistenClass.getName());
return query.list();
} catch (Exception e)
{
return null;
} finally
{
session.close();
} }
}
AbstractDao源码
这个类的代码最重要的就是构造函数。在第一篇泛型的应用里面说了,对于泛型实参的确定有三种方法:①在构造函数显式传入实参类型 ②通过接口显式定义泛型实参,通过反射获取 ③通过匿名子类的方式对泛型实参进行捕获。在这三种方法当中,显然第一种方式不适合使用,因为通用的模板不应该每定义一个接口的时候通过构造器传入泛型实参,这样会导致模板"不通用"。用第三种方法去实现的时候,使用接口编程的时候会让人感觉很突兀,如: IPerson<Person> person = new IPersonDaoImpl(){}; 这样会导致客户端使用难以理解,同时这种匿名子类的方式可能会导致编译器生成好多个匿名子类,这种匿名子类的方式比较适合在方法体里面用来捕获泛型实参。因此选用了第二种方式,同样地因为在接口定义上会以以下形式声明类,所以能够在函数中使用反射获取到签名上的泛型实参,如IPersonDao的定义:
package com.template.dao.interfaces; /**
* @Author: Travelsky_CLSUN
* @Date: Created on 17-5-18
* @Description: 带泛型实参的具体接口定义
*/
public interface IPersonDao<Person> extends IDao<Person>
{ }
IPerson<T>接口的声明
正因为显式地声明了泛型实参,所以可以通过反射获取到实参类型。
package com.template.dao.impl; import com.template.dao.interfaces.IPersonDao;
import com.template.domain.Person; /**
* @Author: Travelsky_CLSUN
* @Date: Created on 17-5-18
* @Description: 接口的具体实现,继承Abstract里面的大部分方法的同时,可以在方法体内编写关于自身使用的方法
*/
public class PersonDaoImpl extends AbstractDao<Person> implements IPersonDao<Person>
{ }
PersonDaoImpl:接口方法的实现
在调用PersonImpl的时候,代码会隐式调用父类的构造函数,在AbstractDao构造器能够收到指向PersonDaoImpl的this,所以在AbstractDao里面可以拿到抽象类的泛型实参签名。因此能够拿到Person这个实参。
通过在AbstractDao里面加入以下两句:

会有以下结果:
what is this? com.template.dao.impl.PersonDaoImpl@232934a1
what is this’s superClass? com.template.dao.impl.AbstractDao<com.template.domain.Person>
抽象类的初始化结果
通过this关键字,巧妙地传递了相关的类型内容。
同样地,这样设计也是有缺点的,①接口如果一直增加,代码声明会越来越多,Hibernatefactory里面的获取的方法也会越来越多。②HibernateFactory未能有效地控制具体的实现类的初始化,可以直接绕开HibernateFactory直接初始化,如可以使用: IPersonDao<Person> personDao = new PersonDaoImpl(); 直接初始化特定的dao,虽然可以在PersonDaoImpl里面把构造函数的修饰符设置成protected,但是每个接口在声明的时候都需要添加这样一个构造函数。
另外,补充一些题外点:
1. 与Hibernate的一级缓存相关的save、update方法必须要开启事务,否者没法同步数据到数据库,就算Hibernate执行的时候打印出了insert语句。
2. 不要在抽象类里面通过属性频繁开关session,这样可能会由于并发导致异常。
3. 使用Hibernate更新数据库的时候,可以通过行级锁把数据锁上,免得两个不同的线程同时修改数据(所以第1点Hibernate在save和update的时候必须使用事务就是这原因)
最后,附上源码,期望对自己和各位的学习有帮助,有问题请及时联系我加以指正,谢谢。
泛型理解及应用(二):使用泛型编写通用型Dao层的更多相关文章
- <十六>JDBC_使用 DBUtils 编写通用的DAO
接口 : DAO<T>.java import java.sql.Connection;import java.sql.SQLException;import java.util.List ...
- java反射+java泛型,封装BaseDaoUtil类。供应多个不同Dao使用
当项目是ssh框架时,每一个Action会对应一个Service和一个Dao.但是所有的Ation对应的Dao中的方法是相同的,只是要查的表不一样.由于封装的思想,为了提高代码的重用性.可以使用jav ...
- Map接口下的集合和泛型理解
一.Map接口 1. Map接口就是最顶层了,上面没有继承了.Map是一个容器接口,它与前面学的List.Set容器不同的是前面学的这些容器,一次只能传入一个元素,但是Map容器一次可以传入一对元素( ...
- .net泛型理解
泛型简介: 泛型(Generic Type)是.NET Framework2.0最强大的功能之一.泛型的主要思想是将算法与数据结构完全分离开,使得一次定义的算法能作用于多种数据结构,从而实现高度可重用 ...
- 转载.net泛型理解说明
net泛型理解 泛型简介: 泛型(Generic Type)是.NET Framework2.0最强大的功能之一.泛型的主要思想是将算法与数据结构完全分离开,使得一次定义的算法能作用于多种数据结构,从 ...
- java基础(十二 )-----Java泛型详解
本文对java的泛型的概念和使用做了详尽的介绍. 概述 泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用. 什么是泛型?为什么要使用泛型? 泛型,即“参数化类型”.一提到 ...
- java泛型理解。代码更明了。
泛型数据java基础,但真正理解需要悉心品尝.毕竟在工作中用到的是在是太多了. 不要以为new ArrayList<>这就是泛型,这只能属于会使用. 在工作中,相对于现有的项目源码的数据库 ...
- C#泛型理解(一)
一.什么是泛型 泛型是C#语言和公共语言运行库(CLR)中的一个新功能,它将类型参数的概念引入.NET Framework.类型参数使得设计某些类和方法成为可能,例如,通过使用泛型类型参数T,可以大大 ...
- Java基础一篇过(二)泛型
一.啥是泛型 概述 泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,即所操作的数据类型被指定为一个参数. 格式 类名<类型名> 标记符 E - Element (在集合中使用 ...
随机推荐
- 正则RegExp的懒惰性和贪婪性; 分组捕获;
1.正则的懒惰性??? 每次在它的方法exec中捕获的时候,只捕获第一次匹配的内容,而不往下捕获,我们把这种情况称为正则的懒惰性 且每一次捕获的位置都是从索引0开始 正则的实例对象上有一个lastin ...
- etcd-v2第二集
参考文章:https://github.com/coreos/etcd/blob/master/Documentation/v2/api.mdhttp://www.cnblogs.com/zhengr ...
- The best way to use Xtool X100 PAD2 for FEM programming
Look here: XTOOL X100 PAD2 is new FEM programming. Possible to use Xtool X100 PAD2 for FEM programmi ...
- NCUAP 利用java自带方法实现导入excel取数据
final JComponent parent = getModel().getContext().getEntranceUI(); JFileChooser chooser = new JFileC ...
- Android使用ksoap2调用C#中的webservice实现图像上传
目录: 一. android使用ksoap2调用webservice 二. 异步调用 三. Android使用ksoap2调用C#中的webservice实现图像上传参考方法 四. 图像传输中Base ...
- ScheduledExecutorService--目前最理想的定时任务实现方式
ScheduledExecutorService 理想的定时任务实现方式 : 通过线程池的方式来执行任务的 可以灵活的设定第一次执行任务延迟时间 提供了良好的约定,以便设定定时执行的间隔时间代码实现: ...
- sas通过IMPORT过程读取外部文件数据
SAS通过IMPORT过程读取外部文件数据 使用IMPORT过程导入带分隔符的文件外,Microsoft Access数据库文件.Miscrosft Excel工作簿. dBase文件.JMP文件.S ...
- javascript Date对象扩展相关function
本篇均以es5为主: 1,月份加减来推日期 // 根据所给月份往后推出日期 function getMonth(count) { var date = new Date(); var year = d ...
- boost--日期处理
1.timer 不同于系统函数的timer()一般生成一个定时器,boost中的timer是一个计时器,以秒为单位,最小精度为毫秒,使用需要包含头文件"boost\timer.hpp&quo ...
- drf8 解析器
解析器的介绍 解析器的作用就是服务端接收客户端传过来的数据,把数据解析成自己想要的数据类型的过程. 本质就是对请求体中的数据进行解析. Accept与ContentType请求头. Accept是告诉 ...