泛型理解及应用(二):使用泛型编写通用型Dao层
相信目前所有的IT公司网站在设计WEB项目的时候都含有持久层,同样地使用过Hibernate的程序员都应该看过或者了解过Hibernate根据数据库反向生成持久层代码的模板。对于Hibernate生成的这个通用型的模板,可以看一下了解Hibernate是怎么处理这一层的。笔者来到公司作为开发的时候,项目已经有了一个比较成型的Dao层代码。当然这个层级代码是用C#+NHibernate写的。在这里刚好用了泛型把整个代码改成使用Java+Hibernate去实现。
首先,先大致阐述下整个通用Dao层是怎么设计的。
具体的类图如下:
大致的设计思路:
1. 工厂类:用来注册Dao层里面的每一个接口,通过HibernateFactory获取到每个具体Dao的实例;实现面向接口式编程。
2. 抽象层:使用泛型抽象出一个Dao层面的接口,使得每个具体的类操作都有自己的Dao实例进行操作。
3. 使用虚拟类对抽象层进行重写,没必要为每个Dao编写一次重复的代码。
对于这套模板,大部分的都是概念性的东西,说白了这个设计里面最重要的就是AbstractDao<T>这个类,因为所有的代码基本都囊括在这里面了。下面,来贴下这个类里面的代码。
1 package com.template.dao.interfaces; 2 3 import java.io.Serializable; 4 import java.util.List; 5 6 /** 7 * @Author: Travelsky_CLSUN 8 * @Date: Created on 17-5-18 9 * @Description: 抽象一个Dao层级内容,含有基本的增删改查功能 10 */ 11 public interface IDao<T> 12 { 13 14 void add(T t); 15 16 void delete(Serializable id); 17 18 void update(T t); 19 20 T findById(Serializable id); 21 22 List<T> findAll(); 23 24 }
1 package com.template.dao.interfaces; 2 3 import com.template.domain.Person; 4 5 /** 6 * @Author: Travelsky_CLSUN 7 * @Date: Created on 17-5-18 8 * @Description: Dao工厂,用来注册每个不同的Dao 9 */ 10 public interface IDaoFactory 11 { 12 IPersonDao<? extends Person> getPersonDao(); 13 }
Dao层工厂的接口,抽象出每个不同的接口,在这里主要用来作为不同接口的注册。在使用其实现工厂HibernateFactory时,必须先在该工厂里面注册。
1 package com.template.dao.impl; 2 3 import com.template.dao.interfaces.IDaoFactory; 4 import com.template.dao.interfaces.IPersonDao; 5 import com.template.domain.Person; 6 7 public class HibernateFactory implements IDaoFactory 8 { 9 public static HibernateFactory factoryInstance = null; 10 11 public static HibernateFactory getFactory() 12 { 13 if (factoryInstance == null) 14 { 15 factoryInstance = new HibernateFactory(); 16 } 17 return factoryInstance; 18 } 19 20 private HibernateFactory() 21 { 22 23 } 24 25 @Override 26 public IPersonDao<Person> getPersonDao() 27 { 28 return new PersonDaoImpl(); 29 } 30 }
HibernateFactory,主要是用来获取每个接口的实现,实现面向接口方式的编程。
1 package com.template.dao.impl; 2 3 import java.io.Serializable; 4 import java.lang.reflect.ParameterizedType; 5 import java.lang.reflect.Type; 6 import java.util.List; 7 8 import org.hibernate.Query; 9 import org.hibernate.Session; 10 import org.hibernate.Transaction; 11 12 import com.template.dao.interfaces.IDao; 13 import com.template.utils.HibernateUtil; 14 15 /** 16 * @Author: Travelsky_CLSUN 17 * @Date: Created on 17-5-18 18 * @Description: 抽象的Dao层,用于实现每个不同Dao上面的实现,避免重复编写代码 19 */ 20 public abstract class AbstractDao<T> implements IDao<T> 21 { 22 Type persistenType; 23 Class<? extends Object> persistenClass; 24 25 // 定义事务 26 Transaction transaction = null; 27 Session session = null; 28 29 public AbstractDao() 30 { 31 Type superClassType = this.getClass().getGenericSuperclass(); 32 if (null != superClassType && superClassType instanceof ParameterizedType) 33 { 34 persistenType = ((ParameterizedType) superClassType).getActualTypeArguments()[0]; 35 persistenClass = (Class<?>) persistenType; 36 } 37 else 38 { 39 System.out.println("类:" + this.getClass().getName() + "尚未实现接口或者定义错误"); 40 } 41 } 42 43 @Override 44 public void add(T t) 45 { 46 47 // 获取session,让每个进来的都应该有一个连接 48 session = HibernateUtil.getSession(); 49 transaction = session.beginTransaction(); 50 try 51 { 52 // save把内容放进hibernate一级缓存 53 session.save(t); 54 // 没进行事务提交的话,hibernate不会把数据同步到数据库,即使是调用session.flush 55 transaction.commit(); 56 } catch (Exception e) 57 { 58 e.printStackTrace(); 59 transaction.rollback(); 60 } finally 61 { 62 transaction = null; 63 session.close(); 64 } 65 66 } 67 68 @Override 69 public void delete(Serializable id) 70 { 71 // 获取session,让每个进来的都应该有一个连接 72 session = HibernateUtil.getSession(); 73 transaction = session.beginTransaction(); 74 Object obj = session.get(persistenClass, id); 75 try 76 { 77 if (null != obj) 78 { 79 session.delete(obj); 80 transaction.commit(); 81 } 82 } catch (Exception e) 83 { 84 transaction.rollback(); 85 } finally 86 { 87 transaction = null; 88 session.close(); 89 } 90 } 91 92 @Override 93 public void update(T t) 94 { 95 // 获取session,让每个进来的都应该有一个连接 96 session = HibernateUtil.getSession(); 97 session.save(t); 98 } 99 100 @Override 101 public T findById(Serializable id) 102 { 103 // 获取session,让每个进来的都应该有一个连接 104 session = HibernateUtil.getSession(); 105 try 106 { 107 Object obj = session.get(persistenClass, id); 108 return (T) obj; 109 } catch (Exception e) 110 { 111 return null; 112 } finally 113 { 114 session.close(); 115 } 116 117 } 118 119 @Override 120 public List<T> findAll() 121 { 122 // 获取session,让每个进来的都应该有一个连接 123 session = HibernateUtil.getSession(); 124 try 125 { 126 Query query = session.createQuery("from " + persistenClass.getName()); 127 return query.list(); 128 } catch (Exception e) 129 { 130 return null; 131 } finally 132 { 133 session.close(); 134 } 135 136 } 137 }
这个类的代码最重要的就是构造函数。在第一篇泛型的应用里面说了,对于泛型实参的确定有三种方法:①在构造函数显式传入实参类型 ②通过接口显式定义泛型实参,通过反射获取 ③通过匿名子类的方式对泛型实参进行捕获。在这三种方法当中,显然第一种方式不适合使用,因为通用的模板不应该每定义一个接口的时候通过构造器传入泛型实参,这样会导致模板"不通用"。用第三种方法去实现的时候,使用接口编程的时候会让人感觉很突兀,如: IPerson<Person> person = new IPersonDaoImpl(){}; 这样会导致客户端使用难以理解,同时这种匿名子类的方式可能会导致编译器生成好多个匿名子类,这种匿名子类的方式比较适合在方法体里面用来捕获泛型实参。因此选用了第二种方式,同样地因为在接口定义上会以以下形式声明类,所以能够在函数中使用反射获取到签名上的泛型实参,如IPersonDao的定义:
1 package com.template.dao.interfaces; 2 3 /** 4 * @Author: Travelsky_CLSUN 5 * @Date: Created on 17-5-18 6 * @Description: 带泛型实参的具体接口定义 7 */ 8 public interface IPersonDao<Person> extends IDao<Person> 9 { 10 11 }
正因为显式地声明了泛型实参,所以可以通过反射获取到实参类型。
1 package com.template.dao.impl; 2 3 import com.template.dao.interfaces.IPersonDao; 4 import com.template.domain.Person; 5 6 /** 7 * @Author: Travelsky_CLSUN 8 * @Date: Created on 17-5-18 9 * @Description: 接口的具体实现,继承Abstract里面的大部分方法的同时,可以在方法体内编写关于自身使用的方法 10 */ 11 public class PersonDaoImpl extends AbstractDao<Person> implements IPersonDao<Person> 12 { 13 14 }
在调用PersonImpl的时候,代码会隐式调用父类的构造函数,在AbstractDao构造器能够收到指向PersonDaoImpl的this,所以在AbstractDao里面可以拿到抽象类的泛型实参签名。因此能够拿到Person这个实参。
通过在AbstractDao里面加入以下两句:
会有以下结果:
1 what is this? com.template.dao.impl.PersonDaoImpl@232934a1 2 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的时候必须使用事务就是这原因)
最后,附上源码,期望对自己和各位的学习有帮助,有问题请及时联系我加以指正,谢谢。