JPA全称Java Persistence API。JPA通过JDK 5.0注解或XML描述对象关系表的映射关系,并将运行期的实体对象持久化到数据库中(摘自百度百科)。本篇结合自己的项目经历,使用的框架是Play Framework 1.2.7,其JPA底层采用的是Hibernate 3.6的实现,来讲述基于JPA构建一个泛型DAO(Data Access Object)。
背景知识
关于JPA就不多做介绍了,如果用过Hibernate或Spring,就会对JPA一目了然。可以点击这里看看JPA概要。我之前也写过一篇介绍在eclipse中集成Hibernate的文章中也提到了DAO泛型编程。
问题来源
项目中有好多实体,比如说老师、学生,还有考试,那么就有对应的DAO叫TeacherDao
、StudentDao
和ExamDao
。虽然JPA对实体提供了方便的find
查询,但是还得在TeacherDao
里写个比如findTeachersBySchool
,在StudentDao
里写个比如findStudentsByTeacher
,或者在ExamDao
里写个findExamsByTeacherAndStudent
等等的方法,而其方法内部通过JPA为实体附加的find
查询来实现。现在的问题是在不同实体对应的DAO类里存在着一堆看起来相似的方法(只是find
里的参数和方法的返回值不同),这就是典型的应该使用泛型的场景。
泛型DAO
1.我们首先要建一个叫GenericDao
的抽象类
1 | public abstract class GenericDao<T, PK extends Serializable> { |
T代表实体类型,PK代表主键类型。
2.实体对应的DAO类去继承GenericDao
1 | public class ExamDao extends GenericDao<Exam, Long> { |
这里Exam
是实体类型,Long
是主键类型。
3.业务层使用DAO时创建具体DAO的实例
1 | ExamDao examDao = new ExamDao(); |
我们系统中所有的DAO都按照这样的规范来写:
a) 可以采用统一实现的方法都写在GenericDao
中
b) 具体DAO中实现各不相同的方法,在GenericDao
定义抽象方法,并在各自的DAO类中实现
c) 具体DAO中特有的方法就写在自己的DAO类里
统一实现的query
1 | public T findById(PK id){ |
这是拍脑袋首先想出来的一些方法,最最简单的增删改查,肯定每个实体的DAO中都要用到。大致看看好像没有问题,但是细思极恐,因为一般系统中用的最多的是按条件且带分页的查询,甚至还带排序,简单一个findAll
真是图样图森破。
1.首先我们要实现一个带分页带排序项的findAll
方法。
1 | public List<T> findAll(int pageNo, String orderBy, String order){ |
这里的find
内部利用Java反射,调用了JPA为具体实体类附加的查询方法。
1 | protected List<T> find(String query, Object[] params, Integer pageNo){ |
这里的pageNo
是Integer
类型,为null
时代表不分页。而query
是我们手工拼成的一个HQL query,下面来看看这个方法。
1 | private String getHQLString(String[] columns, String[] signs, String orderBy, String order){ |
这里columns
表示查询需要比较的字段,而signs
表示比较时的符号(小于、等于、大于等等)。最后返回的HQL形如columnX = ? and columnY < ? orderBy columnZ desc
。最后将这个HQL传给find
,配上实际的参数值(即“?”的填充值),再调用JPA提供的find
来fetch
出相应的结果,以完成分页查询。这个查询过程就如此,注意这里我们将“页”的大小存到了一个常量中。
2.为signs
和order
定义一些常量,以及帮助方法。
1 | public static final String SIGN_EQUALS = "="; |
3.有了以上的基础,我们可以顺势写出好多findBy
方法出来。
1 | public List<T> findBy(String[] columns, Object[] values, String[] signs, int pageNo, String orderBy, String order){ |
4.同理,我们还可以写出findIn
方法。
1 | private String getInHQLString(String column, int inLength, String orderBy, String order){ |
当然我们还能写出findIn
和findBy
混合的方法,即部分字段用符号比较,而部分字段用in
比较。还可以写一堆count
方法,我们这里就不再罗列了。
总结
泛型编程是减少代码冗余的利器,尤其是在DAO层面的泛型,可以将HQL或SQL语句都封装到一个类中,外部使用时只需调用方法和传参即可,降低了程序员写SQL出错的可能性。