Spring 3 MVC: uno-a-muchos dentro de una forma dinámica (agregar / eliminar en crear / actualizar)

Estoy buscando una solución para administrar una relación de uno a muchos dentro de un formulario HTML usando jQuery . Estoy desarrollando Spring , Spring MVC e Hibernate . Encontré muchas pistas en la web, pero no hay ningún ejemplo completo que funcione.

El fondo

Tengo tres entidades JPA:

Consult.java (1)

 @Entity @Table(name = "consult") public class Consult private Integer id; private String label; private Set consultTechnos; /* getters & setters */ } 

ConsultTechno.java (2)

 @Entity @Table(name = "consult_techno") public class ConsultTechno { private Integer id; private Techno techno; private Consult consult; private String level; /* getters & setters */ } 

Techno.java (3)

 @Entity @Table(name="techno") public class Techno { private Integer id; private String label; private Set consultTechnos; /* getters & setters */ } 

Como se muestra, un Consult (1) contiene n ConsultTechnos (2), que se caracterizan por un nivel y un Techno (3).

Las necesidades

Usando un formulario HTML, me gustaría tener un botón Add a techno que agrega dinámicamente dos campos en el DOM:

   

Por supuesto, cada vez que el usuario haga clic en el botón, esos dos campos se deben volver a agregar, etc. Elegí input type="text" para el ejemplo, pero al final, los campos serán dos select .

Se deben cubrir cuatro tipos de operaciones:

  1. Agregue una entidad secundaria al crear una nueva entidad maestra
  2. Eliminar una entidad secundaria al crear una nueva entidad maestra
  3. Agregue una entidad secundaria al actualizar una nueva entidad maestra
  4. Eliminar una entidad hijo al actualizar una nueva entidad maestra

El problema

Esa parte de diseño ya funciona, pero al publicar el formulario, no puedo vincular los campos agregados dinámicamente a mi @ModelAttribute consult .

¿Tienes alguna idea de cómo hacer ese tipo de trabajos? Espero haber sido lo suficientemente claro …

Gracias de antemano :)

Este punto todavía es bastante confuso y poco claro en la web, así que aquí está la manera en que resolví mi problema. Esta solución probablemente no sea la más optimizada, pero funciona al crear y actualizar una entidad maestra.

Teoría

  1. Utilice una List lugar de un Set para sus relaciones de uno a muchos, que deben administrarse dinámicamente.

  2. Inicialice su List como AutoPopulatingList . Es una lista perezosa que permite agregar elementos dinámicamente.

  3. Agregue un atributo remove of int a su entidad hijo. Esto jugará la parte de una bandera booleana y será útil al eliminar dinámicamente un elemento.

  4. Al publicar el formulario, persista solo los elementos que tienen el indicador remove en 0 (es decir, false ).

Práctica

Un ejemplo completo de trabajo: un empleador tiene muchos empleados, un empleado tiene un solo empleador.

Entidades:

Employer.java

 @Entity @Table(name = "employer") public class Employer private Integer id; private String firstname; private String lastname; private String company; private List employees; // one-to-many /* getters & setters */ } 

Employee.java

 @Entity @Table(name = "employee") public class Employee { private Integer id; @Transient // means "not a DB field" private Integer remove; // boolean flag private String firstname; private String lastname; private Employer employer; // many-to-one /* getters & setters */ } 

Controlador:

EmployerController.java

 @Controller @RequestMapping("employer") public class EmployerController { // Manage dynamically added or removed employees private List manageEmployees(Employer employer) { // Store the employees which shouldn't be persisted List employees2remove = new ArrayList(); if (employer.getEmployees() != null) { for (Iterator i = employer.getEmployees().iterator(); i.hasNext();) { Employee employee = i.next(); // If the remove flag is true, remove the employee from the list if (employee.getRemove() == 1) { employees2remove.add(employee); i.remove(); // Otherwise, perform the links } else { employee.setEmployer(employer); } } } return employees2remove; } // -- Creating a new employer ---------- @RequestMapping(value = "create", method = RequestMethod.GET) public String create(@ModelAttribute Employer employer, Model model) { // Should init the AutoPopulatingList return create(employer, model, true); } private String create(Employer employer, Model model, boolean init) { if (init) { // Init the AutoPopulatingList employer.setEmployees(new AutoPopulatingList(Employee.class)); } model.addAttribute("type", "create"); return "employer/edit"; } @RequestMapping(value = "create", method = RequestMethod.POST) public String create(@Valid @ModelAttribute Employer employer, BindingResult bindingResult, Model model) { if (bindingResult.hasErrors()) { // Should not re-init the AutoPopulatingList return create(employer, model, false); } // Call the private method manageEmployees(employer); // Persist the employer employerService.save(employer); return "redirect:employer/show/" + employer.getId(); } // -- Updating an existing employer ---------- @RequestMapping(value = "update/{pk}", method = RequestMethod.GET) public String update(@PathVariable Integer pk, @ModelAttribute Employer employer, Model model) { // Add your own getEmployerById(pk) model.addAttribute("type", "update"); return "employer/edit"; } @RequestMapping(value = "update/{pk}", method = RequestMethod.POST) public String update(@PathVariable Integer pk, @Valid @ModelAttribute Employer employer, BindingResult bindingResult, Model model) { // Add your own getEmployerById(pk) if (bindingResult.hasErrors()) { return update(pk, employer, model); } List employees2remove = manageEmployees(employer); // First, save the employer employerService.update(employer); // Then, delete the previously linked employees which should be now removed for (Employee employee : employees2remove) { if (employee.getId() != null) { employeeService.delete(employee); } } return "redirect:employer/show/" + employer.getId(); } // -- Show an existing employer ---------- @RequestMapping(value = "show/{pk}", method = RequestMethod.GET) public String show(@PathVariable Integer pk, @ModelAttribute Employer employer) { // Add your own getEmployerById(pk) return "employer/show"; } } 

Ver:

employer/edit.jsp

 < %@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>< %@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>< %@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>< %@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>< !DOCTYPE HTML>   Edit            
Firstname
Lastname
company
Employees
remove

Espero que pueda ayudar :)

¿Por qué está usando la etiqueta de entrada HTML en lugar de las formas de taglib de spring?

      

y crear una clase EmployeeDto como, y agregar el modelMap.addAttribute("employeeDto", new Employee); en tu controlador

Bueno, acabo de llegar al problema, la fuente de vista html no mostrará el html dynamic agregado. Si va a inspeccionar los elementos html, el árbol DOM le mostrará todos los elementos dynamics agregados, pero en el formulario Enviar, si ve todos los elementos se enviarán al servidor, incluidos los elementos dynamics creados también.

Una forma de reproducir es ob enviar llamar al método javascript y poner un punto de depuración en el método javascript y verificar los valores de envío de formulario en el documento> elementos de formulario inspeccionando el árbol DOM