之前SSH框架已经搭建完毕,现在进行实体类的分析和Base类的书写。Base类是抽象类,专门用于继承。
一、实体类关系分析
既然是数据采集系统,首先调查实体(Survey)是一定要有的,一个调查有多个页面(Page),一个页面有多个问题(Question),所以还要有页面和问题实体。参与完成调查之后一定还会生成若干个答案,所以还有答案实体(Answer),当然还有参与的用户(User),管理员是特殊的User,只需要登陆的时候进行判断即可。
分析实体类型是比较简单的,最重要的是设计,怎样设计才能满足调查中所需要的各种字段要求?各个实体之间是否有关系,如果有关系是否需要做双向关联(hibernate配置文件中声明)?。
首先从User实体开始分析,一个用户可以参与多个调查,一个调查可以被多个用户参与,所以User和Survey实体之间是典型的多对多关系。既然有有关系了,那么是否需要做双向关联?我们经常做的是根据User对象拿到该User对象拥有的所有Survey,我们基本上不会用到根据Survey对象取得User对象,所以我们只需要做User到Survey的多对多映射关系即可。以上的分析是错误的。这里的User和Survey之间的关系是创建的关系,而不是参与调查的关系。所以一个用户能够创建多个调查,但是一个调查只能被一个用户创建,所以调查和用户之间是多对一关系。虽然我们会通过User对象获取Survey对象集合,但是我们并不会经常这么做,而且这么做有一个致命的缺点,那就是会增加Session的存储压力。为了减小Session的存储压力,我们只做Survey到User的多对一单向关联,不做User到Survey的多对一关联。
Survey实体和Page以及User均有关系,但是不做到User的多对多关系的映射。Survey和Page之间是一对多的关系,一个页面只能出现在一个Survey中,但是一个Survey中能够有多个Page。既然两者有关系,是需要做两者的双向关联关系还是单向关联关系?我们会根据Survey对象获取该对象的所有Page,也会根据该Page获取该页面属于哪一个Survey对象,所以我们需要做双向关联关系,即做Survey到Page的一对多映射和做Page到Survey的多对一映射。
Page实体和Question同理,我们需要做Page到Quesiton的一对多映射,也需要做Question到Page的多对一映射。
Answer实体暂时不作考虑。
二、实体属性分析
1.Question分析(最复杂)
为了能够使用该Question对象保存住所有九种类型的选项,Quesiton中的属性能够表示出这九种类型。
九种类型的题型(排列顺序不能改变,因为需要通过该位置获取问题的类别):
第一类:非矩阵式横向单选按钮、非矩阵式纵向单选按钮、非矩阵式横向复选按钮、非矩阵式纵向复选按钮
第二类:非矩阵式下拉列表
第三类:非矩阵式文本框
第四类:矩阵式单选按钮、矩阵式复选按钮
第五类:矩阵式下拉列表
1 private transient Integer questionId; //问题的ID 2 /** 3 * 题型分为0-8一共九种类型 4 */ 5 private int questionType; //问题的题型 6 private String title; //问题的标题 7 private String optionText; //问题的选项 8 private String[]optionTextArr; //问题选项的集合 9 10 private boolean other; //其他项 11 //其他项可能是无、文本框、下拉列表框 12 private String otherType; //其他项的样式 13 private String otherSelectOptions; //其他项如果是下拉列表框的话使用该项作为内容 14 private String[] otherSelectOptionArr; //该字段对应着其他项是多选框的情况,这里存放着拆分之后的字符串数组 15 16 private String matrixRowTitles; //矩阵式行标题集 17 private String[] matrixRowTitleArr; //矩阵式行标题集数组 18 private String matrixColTitles; //矩阵式列标题集 19 private String[] matrixColTitleArr; //矩阵式列标题集数组 20 private String matrixSelectOptions; //矩阵式下拉选项集 21 private String []matrixSelectOptionArr; //矩阵式下拉列表 22 23 //Question和Page之间是多对一的关系 24 private Page page;
对于每一个问题来说,不可能每个属性都用的到,但是必须要这么写,否则就需要针对每一种提醒设计一种Question实体,那样就麻烦了。
2.Page
private transient Integer pageId; //页面id private String title="未命名"; //页面标题 private String description; //页面描述 //page和调查之间是多对一的关系 private transient Survey survey; //page和Question之间是一对多的关系 private Set<Question> questions=new HashSet<Question>(); private float orderNo; //排序的优先级,默认值和pageId相同
需要说明一个字段是orderNo,该字段的用处是排序,默认值和pageId相同,这个将会在以后的复制/移动页的功能中使用到。
3.Survey分析(复杂)
1 private Integer surveyId; //对应着调查id 2 private String title="未命名"; //对应着调查名称 3 private String preText="上一页"; //对应着翻页的上一个提示 4 private String nextText="下一页"; //对应着下一页的提示 5 private String exitText="退出"; //对应着退出的提示 6 private String doneText="完成"; //对应着完成的提示文本 7 private Date createDate=new Date(); //对应着创建的日期 8 private String logoPath; //使用该字段保存图标的位置,保存的是相对位置 9 //调查和用户之间是多对一的关系 10 private User user; 11 //调查和Page之间是一对多的关系 12 private transient Set<Page>pages=new HashSet<Page>(); 13 14 //添加一个调查是否可用的字段,表示打开或者关闭调查 15 private boolean closed; 16 17 /** 18 * TODO 在数据库库中没有定义,但是需要在配置文件中定义并带到前端页面中使用 19 */ 20 private float maxOrderNo; //最小页序 21 private float minOrderNo; //最大页序 22 23 //定义几个常量,方便判断是哪种类型的提交 24 private String submit_next="下一页"; 25 private String submit_pre="上一页"; 26 private String submit_done="提交"; 27 private String submit_exit="退出";
4.Use类分析
1 private Integer userId; //用户id 2 private String password; //用户密码 3 private String nickName; //用户昵称 4 private String email; //用户邮件 5 private Date registerDate; //用户注册日期 6 private Set<Role>roles; 7 private Boolean superAdmin; //判定是否是超级管理员的标识字段 8 private long[]rightSum; //进行权限判定的关键,注意这里一定要使用基本数据类型,否则会有问题,因为包装类型的默认值不是0,是null
该类在项目初期中只用到了一部分属性,像是基本的userId、password等,剩下的roles、superAdmin、rightSum在权限管理模块中会使用到。
5.hibernate映射文件略。
三、Base类书写。
1.BaseDao书写
针对每一个实体,我们都需要写一个DAO操作对应的数据库中的表,将所有的DAO中的公共方法抽象出来放到一个抽象类中是一个比较好的方法,这样能够极大的重用代码。当然,我们还需要使用一个接口对该抽象类进行规范。
DAO接口规范:
1 package com.kdyzm.dao.base; 2 3 import java.io.Serializable; 4 import java.util.Collection; 5 import java.util.List; 6 7 public interface BaseDao <T>{ 8 //写操作 9 public void saveEntity(T t); 10 public void updateEntity(T t); 11 public void saveOrUpdateEntity(T t); 12 public void deleteEntiry(T t); 13 14 //按照hql批处理 15 public void batchEntityByHql(String hql,Object ...objects); 16 17 //查询方法 18 public T getEntityById(Serializable id); 19 public T loadEntiryById(Serializable id); 20 public List<T> findEntityByHQL(String hql,Object ...objects); 21 public Object findUniqueResult(String hql,Object ...objects); 22 public Collection<T> findAllEntities(); 23 public void executeSql(String sql,Object ...objects); 24 public Collection<T> findAllEntitiesBySql(String sql,Object ...objects); 25 }
实现类(抽象):
1 package com.kdyzm.dao.base.impl; 2 3 import java.io.Serializable; 4 import java.lang.reflect.ParameterizedType; 5 import java.util.Collection; 6 import java.util.List; 7 8 import javax.annotation.Resource; 9 10 import org.hibernate.Query; 11 import org.hibernate.SQLQuery; 12 import org.hibernate.SessionFactory; 13 14 import com.kdyzm.dao.base.BaseDao; 15 /*** 16 * BaseDaoImpl类必须是抽象类,实现已经定义好的接口 17 * @author kdyzm 18 * 19 * @param <T> 20 */ 21 @SuppressWarnings("unchecked") 22 public abstract class BaseDaoImpl<T> implements BaseDao<T> { 23 //手下需要两个成员变量,这两个成员变量的赋值,一个是通过spring容器管理,一个是通过泛型动态获取 24 @Resource(name="sessionFactory") 25 public SessionFactory sessionFactory; 26 private Class<T> clazz; 27 28 //在默认构造方法中调用相关程序获取真实的泛型类型 29 public BaseDaoImpl() { 30 ParameterizedType parameterizedType=(ParameterizedType) this.getClass().getGenericSuperclass(); 31 clazz=(Class<T>) parameterizedType.getActualTypeArguments()[0]; 32 } 33 @Override 34 public void saveEntity(T t) { 35 System.out.println("将要保存"+t); 36 this.sessionFactory.getCurrentSession().save(t); 37 } 38 39 @Override 40 public void updateEntity(T t) { 41 this.sessionFactory.getCurrentSession().update(t); 42 } 43 44 @Override 45 public void saveOrUpdateEntity(T t) { 46 this.sessionFactory.getCurrentSession().saveOrUpdate(t); 47 } 48 49 @Override 50 public void deleteEntiry(T t) { 51 this.sessionFactory.getCurrentSession().delete(t); 52 } 53 54 //批量处理更新的方法重点是使用Query对象 55 @Override 56 public void batchEntityByHql(String hql, Object... objects) { 57 Query query=this.sessionFactory.getCurrentSession().createQuery(hql); 58 for(int i=0;i<objects.length;i++){ 59 query.setParameter(i, objects[i]); 60 } 61 query.executeUpdate(); 62 } 63 64 @Override 65 public T getEntityById(Serializable id) { 66 return (T) this.sessionFactory.getCurrentSession().get(clazz,id); 67 } 68 69 @Override 70 public T loadEntiryById(Serializable id) { 71 return (T) this.sessionFactory.getCurrentSession().load(clazz, id); 72 } 73 74 @Override 75 public List<T> findEntityByHQL(String hql, Object... objects) { 76 Query query=this.sessionFactory.getCurrentSession().createQuery(hql); 77 for(int i=0;i<objects.length;i++){ 78 query.setParameter(i, objects[i]); 79 } 80 return query.list(); 81 } 82 @Override 83 public Object findUniqueResult(String hql, Object... objects) { 84 Query query=this.sessionFactory.getCurrentSession().createQuery(hql); 85 for(int i=0;i<objects.length;i++){ 86 query.setParameter(i, objects[i]); 87 } 88 return query.uniqueResult(); 89 } 90 @Override 91 public Collection<T> findAllEntities(){ 92 String hql="from "+clazz.getSimpleName(); 93 return this.sessionFactory.getCurrentSession().createQuery(hql).list(); 94 } 95 //直接执行sql语句的方法 96 @Override 97 public void executeSql(String sql, Object... objects) { 98 SQLQuery sqlQuery=this.sessionFactory.getCurrentSession().createSQLQuery(sql); 99 for(int i=0;i<objects.length;i++){ 100 sqlQuery.setParameter(i, objects[i]); 101 } 102 sqlQuery.executeUpdate(); 103 } 104 //根据sql语句得到List集合的方法 105 @Override 106 public Collection<T> findAllEntitiesBySql(String sql, Object... objects) { 107 SQLQuery sqlQuery=this.sessionFactory.getCurrentSession().createSQLQuery(sql); 108 for(int i=0;i<objects.length;i++){ 109 sqlQuery.setParameter(i, objects[i]); 110 } 111 sqlQuery.addEntity(clazz); 112 return sqlQuery.list(); 113 } 114 }
实现类需要解决的问题:实现所有的公共方法是其功能要求,想要实现这一点,就必须解决一个最重要的问题,如何获取泛型类型,所有的DAO都会提供一个泛型给父类,即BaseDaoImpl,父类必须知道该类型是什么,在构造方法中获取该类型是最合适的:
//在默认构造方法中调用相关程序获取真实的泛型类型 public BaseDaoImpl() { ParameterizedType parameterizedType=(ParameterizedType) this.getClass().getGenericSuperclass(); clazz=(Class<T>) parameterizedType.getActualTypeArguments()[0]; }
这样clazz对象就保存到了类中的成员变量,其它方法就能够直接使用该对象了。
2.BaseService书写
BaseService接口中的方法和DAO中的方法相同,实现类中直接调用DAO中的方法
1 package com.kdyzm.service.base; 2 3 import java.io.Serializable; 4 import java.util.Collection; 5 import java.util.List; 6 7 public interface BaseService<T> { 8 //写操作 9 public void saveEntity(T t); 10 public void updateEntity(T t); 11 public void saveOrUpdateEntity(T t); 12 public void deleteEntiry(T t); 13 14 //按照hql批处理 15 public void batchEntityByHql(String hql,Object ...objects); 16 17 //查询方法 18 public T getEntityById(Serializable id); 19 public T loadEntiryById(Serializable id); 20 public List<T> findEntityByHQL(String hql,Object ...objects); 21 public Collection<T> findAllEntities(); 22 23 public void executeSql(String sql,Object ...objects); 24 public Collection<T> findAllEntitiesBySQl(String sql,Object ...objects); 25 }
BaseServiceImpl实现:实现类中有一个BaseDao<T>类型的成员变量,所有的DAO都实现了BaseDao接口,所以直接使用该接口来引用子类对象是可以的,但是拿到泛型的方式并不是DAO中的实现方式,而是直接由实现类通过set方法传递过来。
public BaseDao<T> baseDao; public void setBaseDao(BaseDao<T> baseDao) { this.baseDao = baseDao; }
四、针对各种DAO和Service的实现类略。
五、测试
1.实现加入log4j的配置文件到classpath,方便查看控制台输出。
1 ### direct log messages to stdout ### 2 log4j.appender.stdout=org.apache.log4j.ConsoleAppender 3 log4j.appender.stdout.Target=System.out 4 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 5 log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n 6 7 ### direct messages to file hibernate.log ### 8 #log4j.appender.file=org.apache.log4j.FileAppender 9 #log4j.appender.file.File=hibernate.log 10 #log4j.appender.file.layout=org.apache.log4j.PatternLayout 11 #log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n 12 13 ### set log levels - for more verbose logging change 'info' to 'debug' ### 14 15 log4j.rootLogger=warn, stdout 16 17 #log4j.logger.org.hibernate=info 18 log4j.logger.org.hibernate=info 19 20 ### log HQL query parser activity 21 #log4j.logger.org.hibernate.hql.ast.AST=debug 22 23 ### log just the SQL 24 #log4j.logger.org.hibernate.SQL=debug 25 26 ### log JDBC bind parameters ### 27 log4j.logger.org.hibernate.type=info 28 #log4j.logger.org.hibernate.type=debug 29 30 ### log schema export/update ### 31 log4j.logger.org.hibernate.tool.hbm2ddl=debug 32 33 ### log HQL parse trees 34 #log4j.logger.org.hibernate.hql=debug 35 36 ### log cache activity ### 37 #log4j.logger.org.hibernate.cache=debug 38 39 ### log transaction activity 40 #log4j.logger.org.hibernate.transaction=debug 41 42 ### log JDBC resource acquisition 43 #log4j.logger.org.hibernate.jdbc=debug 44 45 ### enable the following line if you want to track down connection ### 46 ### leakages when using DriverManagerConnectionProvider ### 47 #log4j.logger.org.hibernate.connection.DriverManagerConnectionProvider=trace
2.测试类
1 public class TestUserService { 2 private static ApplicationContext ac = null ; 3 4 @BeforeClass 5 public static void iniAC(){ 6 ac = new ClassPathXmlApplicationContext("spring/applicationContext.xml"); 7 } 8 9 @Test 10 public void insertUser(){ 11 UserService us = (UserService) ac.getBean("userService"); 12 User u = new User(); 13 u.setEmail("[email protected]"); 14 u.setPassword("123456"); 15 us.saveEntity(u); 16 } 17 }