Announcement Announcement Module
Collapse
No announcement yet.
Edit one-to-many relation within a single form in MVC 3 Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Edit one-to-many relation within a single form in MVC 3

    Hi people,

    I have two simple classes with a one to many relation :

    Code:
    @Entity
    public class Request extends AbstractEntity {
      
      @OneToMany(mappedBy="request", cascade= CascadeType.ALL)
      protected List<Message> messages;
      
      public Request() {
        messages = new ArrayList<Message>();
      }
      
      public void addMessage(Message message) {
        message.setRequest(this);
        messages.add(message);
      }
    
      public List<Message> getMessages() {
        return messages;
      }
    
      public void setMessages(List<Message> messages) {
        for(Message message : messages) {
          addMessage(message);
        }
      }
    
    }
    Code:
    @Entity
    public class Message extends AbstractEntity {
      
      @ManyToOne
      protected Request request;
      
      public Request getRequest() {
        return request;
      }
    
      public void setRequest(Request request) {
        this.request = request;
      }
    
    }
    Both extending the AbstractEntity that handles to PK stuff etc.
    I've got UT that checks that everything works :

    Code:
      @Test
      public void iCanAddMessageToARequest() {
        Request request = getTestRequest();
        Message msg = new Message();
        
        msg.setContent("myContent");
        msg.setSender("mySender");
        
        request.addMessage(msg);
        Request otherRequest = requestDao.persist(request);
        
        assertNotNull(otherRequest.getMessages());
        assertFalse(otherRequest.getMessages().isEmpty());
        
        Message otherMsg = otherRequest.getMessages().get(0);
        assertEquals(otherMsg.getContent(), msg.getContent());
        assertEquals(otherMsg.getSender(), msg.getSender());
        assertNotNull(otherMsg.getCreatedAt());
      }
    That works great. My problem is that I created a form to create a new Request with a first message.

    The controller :
    Code:
    @Controller
    public class RequestController {
    
      @Resource
      protected RequestDAO requestDao;
    
      @RequestMapping(value = "/", method = RequestMethod.GET)
      public String index(Locale locale, Model model) {
        model.addAttribute("listRequests", requestDao.findAll());
        return "index";
      }
    
      @RequestMapping(value = "/new", method = RequestMethod.GET)
      public String newForm(Model model) {
        model.addAttribute("request", new Request());
        return "new";
      }
    
      @RequestMapping(value = "/", method = RequestMethod.POST)
      public String create( 
        @Valid @ModelAttribute("request") Request request, BindingResult requestBindingResult, 
        Model model) throws IOException {
        
        if (requestBindingResult.hasErrors()) {
          return "new";
        }
        
        requestDao.persist(request);
        model.addAttribute("listRequests", requestDao.findAll());
        return "index";
      }
    
    }
    And the JSP :
    Code:
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
    <%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
    <%@ taglib uri="http://java.sun.com/jstl/fmt" prefix="fmt" %>
    <%@ page session="false" %>
    <html>
      <body>
        <spring:url value="/" var="formUrl" />
        <form:form commandName="request" cssClass="form-horizontal" action="${formUrl}" enctype="multipart/form-data">
          <fieldset>
            <div class="control-group">
              <label class="control-label" for="messages0.content">Message :</label>
              <div class="controls">
                <form:textarea path="messages[0].content" />
              </div>
            </div>
            <div class="form-actions">
              <button type="submit" class="btn btn-primary">Envoyer</button>
              <button class="btn">Annuler</button>
            </div>
          </fieldset>
        </form:form>
      </body>
    </html>
    With all this stuff, both the Request AND the Message are created. But the link between the message and the request is not created properly by Spring. The request_id in the Message table stays empty.

    After some debugging, I understand that this line in the JSP "messages[0].content" results in request.getMessages().add(new Message()).
    What can I do so that the link is properly made between the two ?
    I tried the AutoPopulatingList but it only works with something like request.getMessages().get(index)

    Help please :/

  • #2
    For starters never expose the internal collection directly make it unmodifiable, doing this will result in an error for your current solution.

    When setting up the form add the message yourself by using the appropriate method that will take care of setting the bi-directional relationship.

    Comment


    • #3
      Hi,

      Thanks for your answer but I didn't really get your first point: do you mean I shouldn't have a getter & setter like I did ?

      I tried to add the message when setting up the form as you suggested but nothing changes.
      From what I see from debugging, my "problem" is located in the BeanWrapperImpl:

      Code:
      private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
      ...
      else if (value instanceof List) {
        int index = Integer.parseInt(key);						
        List list = (List) value;
        growCollectionIfNecessary(list, index, indexedPropertyName, pd, i + 1);						
        value = list.get(index);
      }
      As my messages list is empty, it creates a new message and add it.
      How could I avoid this ? I tried using a PropertyEditorSupport but I couldn't bind it correctly in my @InitBinder...

      Comment


      • #4
        No it isn't.. The problem is it only adds it to the set, there is only one side of the relation ship that gets set, not both. You should store the object in the session (SessionAttribute) or create a method annotated with @ModelAttribute to create the new form which properly adds the message.

        Regarding the getter and setter indeed. You shouldn't allow the collection to be modified from the outside of the object it is enclosed in (which is what you do if you directly expose the underlying collection) and the same goes for the setter you should never use that to set the collection of a persisted object.

        Comment


        • #5
          Hi,

          We are currently facing the same issue which is mentioned here. We are migrating our applications from Spring 2.5 to Spring 3.1 . I can see there is change in BeanWrapperImpl class in Spring's new 3.1 jar. growCollectionIfNecessary() method has been added which was not there initially. It is actually inserting new value object(with all blank values) at the 0th position of our Dynamic list. and later adding the rows of the Grid. For Exp: If we have two rows in Grid and when we submit, With Spring 3.1 we can see three rows in our controller and first value is blank value object.

          Code Snippet getPropertyValue of Spring 3.1 is already pasted in above thread, I will paste Spring 2.5 code


          Code:

          private Object getPropertyValue(PropertyTokenHolder tokens) throws BeansException {
          ...
          else if (value instanceof List) {
          List list = (List) value;
          value = list.get(Integer.parseInt(key));
          }

          Just wanted to know what is the usage of the new growCollectionIfNecessary() method. And how can we avoid first blank value object getting inserted in our collection. This is the method which is adding the blank collection.

          Note: We have not yet annoted our controllers yet and We are still using the deprecated controllers of Spring 2.5.

          Please guide us on this. Thanks

          Regards
          Shiv

          Comment

          Working...
          X