【Debug日志】1:初学S2SH的一些bug
当我通过反复翻看笔记,解决了下面第一个错误”域模型注入失败”时,我发觉要开始写Debug日志了,有些错误实在太容易忽略。
缺失public无参构造器使域模型注入失败
错误
当执行到:
Login usrLgn = ln_d.validate(ln.getXh(), ln.getPassword());
报ln
空引用异常,它是Action中的成员:
private Login ln;
本应通过域模型注入进来,反复检查找不到错。
解决
在笔记11有总结过:
在域模型注入时就是要先用这个无参构造器建立出一个对象,再用setter方法往里面传值的
检查我的Login类(一个PO类),确实因为只写了有参构造器覆盖了原来的无参构造器,手动写一个即可。注意需要是public类型,才能控制反转给框架。
错把HQL当SQL尝试操作表和字段
错误
当执行到:
Query qry = sssn.createQuery("from LOGIN where XH=? and PASSWORD=?");
出现错误:
org.hibernate.hql.internal.ast.QuerySyntaxException: LOGIN is not mapped [from LOGIN where XH=? and PASSWORD=?]
解决
因为HQL逻辑上操作的是PO对象和对象的属性,而不是数据库表和表中字段。应当改为类名和属性名:
Query qry = sssn.createQuery("from Login where xh=? and password=?");
javassit的jar包冲突
错误
当执行到:
Login lgn = (Login) qry.uniqueResult();// 执行查询
报错:
java.lang.ClassCastException: myModel.Student_$$_javassist_1 cannot be cast to javassist.util.proxy.Proxy
解决
一个Hibernate的,一个Sturts2的:
删去一个低版本的,然后祈求高版本的能向下兼容即可。
文件上传缺少辅助对象 & 表单提交重复
错误
修改个人信息,表单中可以上传文件:
提交时总是返回INPUT视图。检查控制台发现警告:
严重: Developer Notification (set struts.devMode to false to disable this message):
Unexpected Exception caught setting 'phoContentType' on 'class myAction.MyInfoAction: Error setting expression 'phoContentType' with value ['image/png', ]
五月 11, 2018 10:00:26 上午 com.opensymphony.xwork2.interceptor.ParametersInterceptor error
严重: Developer Notification (set struts.devMode to false to disable this message):
Unexpected Exception caught setting 'phoFileName' on 'class myAction.MyInfoAction: Error setting expression 'phoFileName' with value ['照片_去文字.png', ]
五月 11, 2018 10:00:26 上午 com.opensymphony.xwork2.interceptor.ParametersInterceptor error
严重: Developer Notification (set struts.devMode to false to disable this message):
Unexpected Exception caught setting 'stu.stuId' on 'class myAction.MyInfoAction: Error setting expression 'stu.stuId' with value ['1', '刘知昊', ]
五月 11, 2018 10:00:26 上午 com.opensymphony.xwork2.util.LocalizedTextUtil warn
警告: Missing key [invalid.fieldvalue.stuId] in bundles [[org/apache/struts2/struts-messages, com/opensymphony/xwork2/xwork-messages]]!
五月 11, 2018 10:00:26 上午 com.opensymphony.xwork2.util.LocalizedTextUtil warn
警告: Missing key [invalid.fieldvalue.stu.stuId] in bundles [[org/apache/struts2/struts-messages, com/opensymphony/xwork2/xwork-messages]]!
似乎和照片上传有关,在JSP中这一部分是:
<tr>
<td>照片:</td>
<!-- 上传照片 -->
<td>
<s:file name="pho"/>
</td>
</tr>
在Action中关于它只组合了:
private File pho;// 照片文件
并提供了getter和setter。
解决
为文件上传的File对象提供两个辅助字段:
private String phoContentType;// 该文件对象(pho)的类型
private String phoFileName;// 该文件对象(pho)上传时的文件名
并提供getter和setter,消除了前两个警告。但依然返回INPUT视图,现在警告是:
严重: Developer Notification (set struts.devMode to false to disable this message):
Unexpected Exception caught setting 'stu.stuId' on 'class myAction.MyInfoAction: Error setting expression 'stu.stuId' with value ['1', '刘知昊', ]
五月 11, 2018 10:05:03 上午 com.opensymphony.xwork2.util.LocalizedTextUtil warn
警告: Missing key [invalid.fieldvalue.stuId] in bundles [[org/apache/struts2/struts-messages, com/opensymphony/xwork2/xwork-messages]]!
五月 11, 2018 10:05:03 上午 com.opensymphony.xwork2.util.LocalizedTextUtil warn
警告: Missing key [invalid.fieldvalue.stu.stuId] in bundles [[org/apache/struts2/struts-messages, com/opensymphony/xwork2/xwork-messages]]!
错误信息中说stu.stuId
被传入了一个值列表,里面有一个值1,还有一个值是名字。应该是页面提交问题,检查发现:
<tr>
<td>学号:</td>
<td>
<!-- 学号是只读的,不能修改 -->
<input type="text" name="stu.stuId" value="<s:property value="#request.stu.stuId"/>" readOnly/>
</td>
</tr>
<tr>
<td>姓名:</td>
<td>
<input type="text" name="stu.stuId" value="<s:property value="#request.stu.name"/>" />
</td>
</tr>
在传姓名时的域模型注入目标使用了学号,所以出错,改正即可。
此外,学号既然是只读的,应当只显示不可提交,而不是仅在JSP上使其readOnly
,学号可以在服务器端通过会话查出来。
懒加载导致没有获得成员对象
错误
Struts has detected an unhandled exception:
Messages:
failed to lazily initialize a collection of role: myModel.Course.student, could not initialize proxy - no Session
File: org/hibernate/collection/internal/AbstractPersistentCollection.java
Line number: 575
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: myModel.Course.student, could not initialize proxy - no Session
Course使用了student的集合作为成员,表示一个课有多个选课学生:
private Set<Student> student = new HashSet<Student>();
并且配置了映射文件:
<!-- N课程对应M个学生,加载课程时不必立即加载选课学生,控制反转-->
<set name="student" table="SC" lazy="true" inverse="true">
<key column="COURSEID"/>
<many-to-many class="Student" column="STUID"/>
</set>
意为在加载课程对象时不必立即加载选课学生,且inverse="true"
控制反转将控制权交给学生对象,表示这个N-M映射关系由学生(的选课退课)来维护。
在学生提交个人信息时,为了不改变选课信息,将选课信息重新写到了域模型注入的对象中:
// 学生要提交自己的修改个人信息
public String updtCmmt() throws Exception {
StudentDAO stu_d = new StudentDAOImp();
ProfessDAO pf_d = new ProfessDAOImp();
// 把提交的专业整合到提交的其它信息(Student对象)里
stu.setZy(pf_d.getOneProfess(pf.getPfId()));
// 处理传上来的照片文件
if (this.pho != null) {
// ...把照片存入数据库...
} else {
stu.setZp(stu_d.getOneStu(stu.getStuId()).getZp());
}
// 处理课程(也是直接重新设定一次)
stu.setCourse(stu_d.getOneStu(stu.getStuId()).getCourse());
System.out.println(stu);
// 更新
stu_d.update(stu);
return SUCCESS;
}
解决
获取的课程中显然没有再去包含选课学生,这对单个学生来说也是不必要的。而PO持久化时这样的课程却是异常的,因为其中的学生没有了。
可以先去读出DB中的对象,在Session关闭后成为脱管对象,再在该对象上修改属性再重新变为PO存入DB。
// 学生要提交自己的修改个人信息
public String updtCmmt() throws Exception {
StudentDAO stu_d = new StudentDAOImp();
ProfessDAO pf_d = new ProfessDAOImp();
// 获取PO的脱管对象
Student stu_tmp = stu_d.getOneStu(stu.getStuId());
// 处理专业
stu_tmp.setZy(pf_d.getOneProfess(pf.getPfId()));
// 处理传上来的照片文件
if (this.pho != null) {
// ...把照片存入数据库...
} else {
// 什么都不用做,脱管对象里自己有之前的
}
// 处理其它没有依赖其它对象的直接属性
stu_tmp.setBz(stu.getBz());
stu_tmp.setCssj(stu.getCssj());
stu_tmp.setName(stu.getName());
stu_tmp.setSex(stu.getSex());
stu_tmp.setZp(stu.getZp());
stu_tmp.setZxf(stu.getZxf());
// 课程不需要动,在PO脱管里
// 更新,让脱管对象变回PO,并持久化到数据库
stu_d.update(stu_tmp);
return SUCCESS;
}
照片没有成功存到blob字段中
错误
在Hibernate自顶向下将byte[]生成为tinyblob
后,不能按课本上的方法上传照片。查了一下tinyblob
只能支持最大255B的文件,改为blob(最大65KB)依然不行。
检查了表单,有配置enctype="multipart/form-data"
无误;检查了映射文件中该字段的映射:
<property name="zp" type="binary" column="ZP"/>
Java的byte[]映射类型为binary
,也没有错。
检查了文件对象本身传入Action的情况,编写(注意如果为该文件对象建立了输入流,要先将输入流关闭):
// 存到本地试试
String dataDir = "E:/home/";
File saveFile = new File(dataDir, phoFileName);
pho.renameTo(saveFile);
能够正确上传到本地,说明传入Action也是正常的:
在read之后尝试输出也没有问题:
for (byte b : buffer) {
System.out.println(b + " ");
}
能够输出很多不同的数字。在数据库里改blob的长度也改不了,说明这种大型对象长度是自动的,不是手工设定的,它只有一个最大限制长度。
解决
直到我在后面看到了这句代码:
stu_tmp.setZp(stu.getZp());
被它覆盖了,删去就好。最终这部分的代码:
// 学生要提交自己的修改个人信息
public String updtCmmt() throws Exception {
StudentDAO stu_d = new StudentDAOImp();
ProfessDAO pf_d = new ProfessDAOImp();
// 获取PO的脱管对象
Student stu_tmp = stu_d.getOneStu(stu.getStuId());
// 处理专业
stu_tmp.setZy(pf_d.getOneProfess(pf.getPfId()));
// 处理传上来的照片文件
if (this.pho != null) {
// 存到本地试试
// String dataDir = "E:/home/";
// File saveFile = new File(dataDir, phoFileName);
// pho.renameTo(saveFile);
// 读到byte数组里
InputStream fis = new FileInputStream(pho);
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
// 尝试直接设定给stu_tmp这个脱管对象
stu_tmp.setZp(buffer);
fis.close();
System.out.println("[scss]pho对象不为空");
} else {
// 什么都不用做,脱管对象里自己有之前的
System.out.println("[debug]pho对象为空");
}
// 处理其它没有依赖其它对象的直接属性
stu_tmp.setBz(stu.getBz());
stu_tmp.setCssj(stu.getCssj());
stu_tmp.setName(stu.getName());
stu_tmp.setSex(stu.getSex());
stu_tmp.setZxf(stu.getZxf());
// 课程不需要动,在PO脱管里
// 更新,让脱管对象变回PO,并持久化到数据库
stu_d.update(stu_tmp);
return SUCCESS;
}
上传效果: