1、设计调查选项处理-巧利用Hibernate为对象赋值的时机
1)在Hibernate映射文件的<set>元素中添加order-by属性指定集合元素的排序方式。
2)由于问题的选项在数据库中是以字符串(选项之间以“\r\n”分隔)的形式保存的,因此,在页面上进行问题选项显示前要对字符串进行拆分工作。该字符串拆分的执行时机应该是什么时候比较好?(视频16:调查管理系统_设计调查选项处理)
技巧:Hibernate从DB中查询出数据并映射为对象,在为对象进行赋值时是调用对象成员变量的setter方法的。因此,可以利用Hibernate为对象赋值的时机,重写对象的setter方法,增加相应的的业务逻辑,完成字符串的拆分工作。
Question实体的基本设计如下,其中为每个需要拆分的字段增加了相应的字符串数组与对应的getter与setter方法,并且重写了相应的setter方法,下面以重写setOptions方法为例。
1 package com.atguigu.surveypark.model; 2 import com.atguigu.surveypark.util.StringUtil; 3 import com.atguigu.surveypark.util.ValidateUtil; 4 /** 5 * 问题类 6 */ 7 public class Question { 8 private static final String RN = "\r\n" ; 9 private Integer id;//主键id 10 private int questionType;// 题型0-8 11 private String title;//问题题干 12 private String options;//选项 13 private String[] optionArr ; 14 private boolean other;// 其他项 15 private int otherStyle;// 其他项样式:0-无 1-文本框 2-下拉列表 16 // 其他项下拉选项 17 private String otherSelectOptions; 18 private String[] otherSelectOptionArr ; 19 // 矩阵式行标题集 20 private String matrixRowTitles; 21 private String[] matrixRowTitleArr ; 22 // 矩阵式列标题集 23 private String matrixColTitles; 24 private String[] matrixColTitleArr ; 25 // 矩阵是下拉选项集 26 private String matrixSelectOptions; 27 private String[] matrixSelectOptionArr ; 28 private Page page; //建立从Question到Page之间多对一关联关系 29 /** 30 * 重写setOptions方法完成字符串拆分 31 */ 32 public void setOptions(String options) { 33 this.options = options; 34 this.optionArr = StringUtil.str2Arr(options, RN); 35 } 36 //省略了其它的getter与setter方法 37 }
其中StringUtil工具类中的str2Arr()方法的基本实现如下:
1 //将字符串转换成数组,按照tag分割 2 public static String[] str2Arr(String str,String tag){ 3 if(ValidateUtil.isValid(str)){ 4 return str.split(tag); 5 } 6 return null ; 7 }
2、页面&问题的删除和编辑
1)答案实体Answer的分析与设计
Ⅰ、答案与问题是一对多的关系,且在该系统中,用户考虑的是回答某问题的人数比例等信息,而不是某个人的具体答案。另外,答案实体中存放的是选项的索引字符串(以“,”分隔)以及其它项,另外还可以包括回答问题的时间等。还有很重要的一点是通过一个标识来表示某一批答案是某一个人作答的,因此引入一个uuid字符串来表示一个批次的答案(即同一个人的作答的答案的uuid是相同的)。
Ⅱ、实体之间的关联关系(单/双向)可以建立也可以不建立,要根据需求来考虑。在该体统中,Question与Answer实体之间就不建立关联关系。因为在调查中并不关心某一个人的作答,也并不会去检索某一个实体,主要是使用聚合函数count()去统计答案的比例,这样也就不会由Answer实体导航到Question了,因此不需要建立Answer到Question的关联关系;同样,也不会去查询一个Question的全部作答结果,只是查看统计结果,因此没必要建立Question到Answer之间的关联关系了。不建立关联关系时,其关系的维护只需用普通的字段即可[Q的id],而不需要使用Q的对象了。(在线考试系统等要根据情况另外考虑设计了,可以将正确的答案保存在Q中,回答后进行比较等。)
Ⅲ、Answer实体的基本设计如下:
1 package com.atguigu.surveypark.model; 2 import java.util.Date; 3 /** 4 * Answer 5 */ 6 public class Answer { 7 private Integer id; 8 private String answerIds;// 选项的索引. 9 private String otherAnswer; //其它项 10 private String uuid;// 批次. 11 private Date answerTime; 12 13 private Integer questionId;// 关联字段 14 // 关联字段:页面id(冗余)。注:教程中没有给出该字段是为了演示Hibernate的数据库写操作问题 15 //private Integer pageId; 16 private Integer surveyId;// 关联字段:调查id(冗余)--为提高效率而增加的特殊字段 17 //省略了getter与setter 18 }
2)Hibernate使用中的一些问题
Ⅰ、在系统中当删除问题、页面、调查时,其对应的答案也应该删除。Hibernate虽然提供了级联删除操作,但是其性能很差,它是将待删除对象的所有子节点全部查出来然后一个一个删除的,而不是批量删除。另外,Hibernate虽然提供了级联操作的功能,但是在真正的项目开发中很少使用级联,其会导致我们的数据不可控等复杂问题。
Ⅱ、Hibernate的数据库写操作中不允许两级以上的链接,需要使用子查询的方式代替。如下所示在删除调查时的操作:
1 /** 2 * 删除survey,同时删除page,问题,答案 3 */ 4 public void deleteSurvey(Integer sid){ 5 //delete answers 6 String hql = "delete from Answer a where a.surveyId = ?" ; 7 answerDao.batchEntityByHQL(hql,sid); 8 //hibernate在写操作中,不允许两级以上的链接. 9 //hql = "delete from Question q where q.page.survey.id = ?" ; 10 hql = "delete from Question q where q.page.id in (select p.id from Page p where p.survey.id = ?)" ; 11 questionDao.batchEntityByHQL(hql, sid); 12 //delete page 13 hql = "delete from Page p where p.survey.id = ? " ; 14 pageDao.batchEntityByHQL(hql, sid); 15 //delete survey 16 hql = "delete from Survey s where s.id = ?" ; 17 surveyDao.batchEntityByHQL(hql, sid); 18 }
3、切换状态-打开与关闭调查
1)为了表示当前的调查是开放的还是关闭的为Survey实体添加了一个boolean类型(基本类型,非包装类型)的closed字段,并在映射文件中进行配置。当重新运行程序后,由于在数据库中为closed字段指定的默认值为NULL,hibernate无法将null值赋值给基本数据类型,因此会导致程序启动异常,因此需要手动修改数据库closed字段的值为true或false,才能使程序正常启动。
2)MySQL中保存boolean类型使用的是位类型bit(1),即一个bit位(0或1)。其在命令行中显示时,若值为1即true,则显示的是一个“笑脸”的符号,若为0即false,则不显示。
3)Hibernate的HQL中不支持取反操作,示例如下:
1 String hql = "update Survey s set s.closed = !s.closed where s.id = ?" ;//这种写法是错误的。 2 surveyDao.batchEntityByHQL(hql,sid);
以下是正确的写法,即要将原来的值取反后再以参数的形式传入
1 Survey s = this.getSurvey(sid); 2 String hql = "update Survey s set s.closed = ? where s.id = ?" ; 3 surveyDao.batchEntityByHQL(hql,!s.isClosed(),sid);
4)Java中boolean类型的默认值为false。
5)Hibernate映射文件中的<property>元素的update属性限制的只是面向对象的更新操作即session的update/saveOrUpdate方法,不能限制HQL的更新。
4、经验汇总
1)一般在后台执行了数据库写操作后,都要重定向到一个页面或action,而不要使用转发,否则当用户进行刷新或后退操作时会导致表单重复提交问题。
2)当编辑/修改完成后,在进行保存更新操作时,在后台一般有一下两种处理方式:
Ⅰ、直接将model更新到数据库中,若有外键关联关系则在保存更新之前重新设定;
Ⅱ、先从数据库中查找出原来的对象,再将model中的属性值设置到原来的对象中,之后再保存更新原来的对象。
(要根据修改的字段的多少来选取合适的方式。第一种方式应该可以减少数据库的访问次数,因为一般外键关系对象可以直接new一个只包含id(主键)的对象,设置到model即可,而不需要从数据库中查出。)