在学习的过程中,有一个这样的情况。
添加学生时,需要在下拉框里面显示出班级然后提供选择。当然,实体类里面设置好了映射关系了。也就是student实体类中已经有了clas的属性值并提供getter/setter
在jsp页面上
<s:form action="AddStudentAction" method="post">
<s:textfield label="学号" name="student.std_Id" maxlength="14"></s:textfield><br/>
<s:textfield label="姓名" name="student.std_Name" maxlength="50"></s:textfield>
<s:select label="性别" name="student.sex" list="#{'1':'男','0':'女'}"></s:select>
<sx:datetimepicker label="生日" name="student.birthday" displayFormat="yyyy-MM-dd"></sx:datetimepicker>
<s:select label="选择班级" list="#request.clases" name="c_id" listKey="c_id" listValue="class_Name" headerKey="0" headerValue="班级"></s:select>
<s:submit value="确定添加"></s:submit>
<s:reset value="取消操作"></s:reset>
</s:form>
其他的没什么问题,这里主要是select标签
正确做法:
<s:select label="选择班级" list="#request.clases" name="c_id" listKey="c_id" listValue="class_Name" headerKey="0" headerValue="班级"></s:select>
摸索了一段时候后, 如果写成:
<s:select label="选择班级" list="#request.clases" name="student.clas.c_id" listKey="c_id" listValue="class_Name" headerKey="0" headerValue="班级"></s:select>
JPA或者hibernate是无法帮你做好student和clas之间的关系的。原因是jsp文件执行之后通过了放射机制映射到AddStudentAction中的student,但是,这个时候,student这个对象里面是没有clas这个对象了。所以,在select标签里面设置name="student.clas.c_id"是无法成功添加clas的。
那么,怎么办呢?
我们可以取到select中传过来的c_id,然后在AddStudentAction中根据这个c_id从数据库读取出这个clas。
@Override
public String execute() throws Exception {
HttpServletRequest request = ServletActionContext.getRequest();
String c_idstr=request.getParameter("c_id");
int c_id=0;
if(!c_idstr.trim().equals("")&&c_idstr!=null){
c_id=Integer.parseInt(c_idstr);
// System.out.println("能不能获得select里面的c_id呢?"+c_id);
}
Clas clas=clasDao.getClas(c_id);
// System.out.println("你几班的?"+clas.getClass_Name());
if(student!=null){
student.setClas(clas);
boolean flag=studentDao.addStudent(student);
System.out.println("返回值"+flag);
}
return SUCCESS;
}
对!你发现可以获取到clas了。按理说
student.setClas(clas);
boolean flag=studentDao.addStudent(student);
即可保存到数据库。
但是,却报javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.xiehande.jpa.domain.Clas
原因是,clas对象本身在数据库中有,当我student.setClas(clas)的时候,还没有实例化到数据库就有了ID值,而且这个ID值就是以前存进去的id.这是不正常,因为ID没有设置过,而且JPA默认的行为是只要指定了主键生成策略,主键就不能设置了,一旦不为空或者0就被认为是已经保存到了数据库中,一旦调用persist()方法就会抛出上面的异常。
解决的办法有:
1.更改级联配置:
把cascade = CascadeType.ALL 改成 cascade=CascadeType.REFRESH 。(级联让他刷新就好,id值无冲突,自然可以操作) 但,个人不建议这样做,因为你还是想级联过程多 几种,ALL常被使用。
2.对于Action来说必须采用prototype(每次调用创建一个对象)的作用域,修改方法是:在Action上就一个注解@Scope("prototype")
3.将保存student的方法em.persist(student)改成方法em.merge(student);
public boolean addStudent(Student student) {
boolean flag = false;
if (student != null) {
em = JPAEntityManagerFactory.getEntityManager();
try {
em.getTransaction().begin();
em.merge(student);
em.getTransaction().commit();
flag = true;
} catch (Exception ex) {
ex.printStackTrace();
em.getTransaction().rollback();
flag = false;
} finally {
em.close();
}
}
return flag;
}
这个情况实际上开起来不难,但是,如果不是很有经验的程序员的话,也是挺难发现这种问题。