PDA

View Full Version : @ModelAttribute detached object binding from <form:form> tag has a problem with 3.1.1



beku8
Apr 30th, 2012, 05:18 AM
After migrating to spring 3.1.0 from spring 3.0.5 hibernate save method is encountering problem with detached object - when trying to save new object with saveOrUpdate(Object obj) method where obj has no id.

Here are the code samples and unimportant parts are omitted
Domains:


@Entity
@Table(name = "task")
public class Task implements java.io.Serializable {

private Long id;
private Set<TaskComment> taskComments = new HashSet<TaskComment>(0);

@OneToMany(fetch = FetchType.LAZY, mappedBy = "task", cascade = CascadeType.REMOVE)
@OrderBy("date")
public Set<TaskComment> getTaskComments() {
return this.taskComments;
}

}


and the object I'm really trying to save is task_comment:




@Entity
@Table(name = "task_comment")
public class TaskComment implements java.io.Serializable {

private Long id;
private Task task;
private String content;
public TaskComment() {
}

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "task_id")
public Task getTask() {
return this.task;
}

}



Controller:


@RequestMapping(value="/{id}", method=RequestMethod.GET)
public String getDretail(@PathVariable("id") long id, Model model){
model.addAttribute("task", taskDao.find(id));
model.addAttribute("taskComment", new TaskComment());
return "task-detail";
}

@RequestMapping(value="/{id}", method=RequestMethod.POST)
public String postComment(@PathVariable("id") long id, @ModelAttribute("taskComment") TaskComment taskComment, Model model){
Task task = taskDao.find(id);
taskComment.setUsers(securityService.getCurrentUse r());
taskComment.setTask(task);
taskComment.setDate(new Date());


taskCommentDao.saveOrUpdate(taskComment);

return "redirect:/dashboard/task/" + id;
}


HibernateFilter extends which extends from OpenSessionInViewFilter:



public class HibernateFilter extends OpenSessionInViewFilter {

@Override
protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
//set the FlushMode to auto in order to save objects.
session.setFlushMode(FlushMode.AUTO);
return session;
}

@Override
protected void closeSession(Session session, SessionFactory sessionFactory) {
try{
if (session != null && session.isOpen() && session.isConnected()) {
try {
session.flush();
}
catch (HibernateException e) {
throw new CleanupFailureDataAccessException("Failed to flush session before close: " + e.getMessage(), e);
}
catch(Exception e){
}
}
}
finally{
super.closeSession(session, sessionFactory);
}
}
}


jsp exerpt:


<form:form modelAttribute="taskComment" method="post" cssClass="formFrm" style="padding:0;">
<form:textarea path="content" cssStyle="width:99%;"/>
<input id="submit-btn" type="submit" value="Add comment" class="buttons" style="float:right;margin:0;">
</form:form>


Problem: overriding all the previous comments. In other words, Task domain contains only one taskComment after post which was the last entered. However, when I tried unit testing it had no problem, so I think it's the problem with @ModelAttribute binding.

Work around:



public String postComment(@PathVariable("id") long id, @ModelAttribute("taskComment") TaskComment taskComment, Model model){

TaskComment taskComment2 = new TaskComment();
taskComment2.setContent(taskComment.getContent());
Task task = taskDao.find(id);
taskComment2.setUsers(securityService.getCurrentUs er());
taskComment2.setTask(task);
taskComment2.setDate(new Date());

taskCommentDao.saveOrUpdate(taskComment2);
return "redirect:/dashboard/task/" + id;
}


So I tried in multiple ways to get this working. I wrapped it in a transaction, played with the entity annotations... Only initializing new TaskComment then copying properties from bound taskComment then saving works fine.

I could do this to all my objects but would really painful and ugly refactoring. Any ideas? Please, help.

p.s I'm using hibernate 3.5.6-Final and spring security 3.1.0 and included pom file if it helps.

Marten Deinum
Apr 30th, 2012, 07:52 AM
And the problem is, doesn't it save, do you get an exception what exactly?!

Also not sure why you have your own HibernateFilter, flushing after execution isn't something I would do. In general such a "solution" is only to solve improper transaction management.

Rossen Stoyanchev
May 10th, 2012, 11:42 AM
Have you noticed what's actually different in the content of taskComment and taskComment2? I can't quite see what could could possibly cause it override all the other comments. Without knowing anything else about your controller, I would expect taskComment was created by calling its default constructor. Can you confirm if that's the case through a breakpoint or log statements?

beku8
May 10th, 2012, 08:16 PM
Sorry for not posting back that I figured out the problem.


@PathVariable("id") long id was the problem. The new feature in 3.1 got me confused. Task ID was bound to TaskComment ID,
Thanks for your replies