¿Cómo enlazar una lista de objetos con thymeleaf?

Tengo muchas dificultades para enviar un formulario al controlador, que debe contener simplemente una lista de objetos que el usuario puede editar.

El formulario se carga correctamente, pero cuando se publica, nunca parece publicar nada.

Aquí está mi forma:

<form action="#" th:action="@{/query/submitQuery}" th:object="${clientList}" method="post">

<table class="table table-bordered table-hover table-striped">
<thead>
    <tr>
        <th>Select</th>
        <th>Client ID</th>
        <th>IP Addresss</th>
        <th>Description</th>            
   </tr>
 </thead>
 <tbody>     
     <tr th:each="currentClient, stat : ${clientList}">         
         <td><input type="checkbox" th:checked="${currentClient.selected}" /></td>
         <td th:text="${currentClient.getClientID()}" ></td>
         <td th:text="${currentClient.getIpAddress()}"></td>
         <td th:text="${currentClient.getDescription()}" ></td>
      </tr>
  </tbody>
  </table>
  <button type="submit" value="submit" class="btn btn-success">Submit</button>
  </form>

Lo anterior funciona bien, carga la lista correctamente. Sin embargo, cuando POST, devuelve un objeto vacío (de tamaño 0). Creo que esto se debe a la falta deth:field, pero de todos modos aquí está el método POST del controlador:

...
private List<ClientWithSelection> allClientsWithSelection = new ArrayList<ClientWithSelection>();
//GET method
...
model.addAttribute("clientList", allClientsWithSelection)
....
//POST method
@RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(@ModelAttribute(value="clientList") ArrayList clientList, Model model){
    //clientList== 0 in size
    ...
}

He intentado agregar unth:field pero independientemente de lo que haga, causa una excepción.

He intentado:

...
<tr th:each="currentClient, stat : ${clientList}">   
     <td><input type="checkbox" th:checked="${currentClient.selected}"  th:field="*{}" /></td>

    <td th th:field="*{currentClient.selected}" ></td>
...

No puedo acceder a currentClient (error de compilación), ni siquiera puedo seleccionar clientList, me da opciones comoget(), add(), clearAll() etc., por lo que parece que debería tener una matriz, sin embargo, no puedo pasar una matriz.

También he intentado usar algo comoth:field=${}, esto causa una excepción de tiempo de ejecución

He intentado

th:field = "*{clientList[__currentClient.clientID__]}" 

pero también compila error.

¿Algunas ideas?

ACTUALIZACIÓN 1:

Tobias sugirió que necesito envolver mi lista en un wraapper. Entonces eso fue lo que hice:

ClientWithSelectionWrapper:

public class ClientWithSelectionListWrapper {

private ArrayList<ClientWithSelection> clientList;

public List<ClientWithSelection> getClientList(){
    return clientList;
}

public void setClientList(ArrayList<ClientWithSelection> clients){
    this.clientList = clients;
}
}

Mi página:

<form action="#" th:action="@{/query/submitQuery}" th:object="${wrapper}" method="post">
....
 <tr th:each="currentClient, stat : ${wrapper.clientList}">
     <td th:text="${stat}"></td>
     <td>
         <input type="checkbox"
                th:name="|clientList[${stat.index}]|"
                th:value="${currentClient.getClientID()}"
                th:checked="${currentClient.selected}" />
     </td>
     <td th:text="${currentClient.getClientID()}" ></td>
     <td th:text="${currentClient.getIpAddress()}"></td>
     <td th:text="${currentClient.getDescription()}" ></td>
 </tr>

Por encima de las cargas bien:

Entonces mi controlador:

@RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(@ModelAttribute ClientWithSelectionListWrapper wrapper, Model model){
... 
}

La página se carga correctamente, los datos se muestran como se esperaba. Si publico el formulario sin ninguna selección, obtengo esto:

org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 0): Property or field 'clientList' cannot be found on null

No estoy seguro de por qué se queja

(En el método GET tiene:model.addAttribute("wrapper", wrapper);)

Si luego hago una selección, es decir, marque la primera entrada:

There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='clientWithSelectionListWrapper'. Error count: 1

Supongo que mi controlador POST no está obteniendo el clientWithSelectionListWrapper. No estoy seguro de por qué, ya que configuré el objeto contenedor para que se vuelva a publicar a través deth:object="wrapper" en el encabezado FORM.

ACTUALIZACIÓN 2:

¡He hecho algunos progresos! Finalmente, el formulario enviado está siendo recogido por el método POST en el controlador. Sin embargo, todas las propiedades parecen ser nulas, excepto si el elemento ha sido marcado o no. He realizado varios cambios, así es como se ve:

<form action="#" th:action="@{/query/submitQuery}" th:object="${wrapper}" method="post">
....
 <tr th:each="currentClient, stat : ${clientList}">
     <td th:text="${stat}"></td>
     <td>
         <input type="checkbox"
                th:name="|clientList[${stat.index}]|"
                th:value="${currentClient.getClientID()}"
                th:checked="${currentClient.selected}"
                th:field="*{clientList[__${stat.index}__].selected}">
     </td>
     <td th:text="${currentClient.getClientID()}"
         th:field="*{clientList[__${stat.index}__].clientID}"
         th:value="${currentClient.getClientID()}"
     ></td>
     <td th:text="${currentClient.getIpAddress()}"
         th:field="*{clientList[__${stat.index}__].ipAddress}"
         th:value="${currentClient.getIpAddress()}"
     ></td>
     <td th:text="${currentClient.getDescription()}"
         th:field="*{clientList[__${stat.index}__].description}"
         th:value="${currentClient.getDescription()}"
     ></td>
     </tr>

También agregué un constructor predeterminado sin parámetros a mi clase de contenedor y agregué unbindingResult param al método POST (no estoy seguro si es necesario).

public String processQuery(@ModelAttribute ClientWithSelectionListWrapper wrapper, BindingResult bindingResult, Model model)

Entonces, cuando se publica un objeto, así es como se ve:

Por supuesto, se supone que systemInfo es nulo (en esta etapa), pero el ID de cliente siempre es 0 y ipAddress / Descripción siempre es nulo. Sin embargo, el booleano seleccionado es correcto para todas las propiedades. Estoy seguro de que he cometido un error en una de las propiedades en alguna parte. De vuelta a la investigación.

ACTUALIZACIÓN 3:

Ok, ¡he logrado completar todos los valores correctamente! Pero tuve que cambiar mitd para incluir un<input /> que no es lo que quería ... Sin embargo, los valores se están rellenando correctamente, lo que sugiere que la primavera busca una etiqueta de entrada para el mapeo de datos.

Aquí hay un ejemplo de cómo cambié los datos de la tabla clientID:

<td>
 <input type="text" readonly="readonly"                                                          
     th:name="|clientList[${stat.index}]|"
     th:value="${currentClient.getClientID()}"
     th:field="*{clientList[__${stat.index}__].clientID}"
  />
</td>

Ahora necesito descubrir cómo mostrarlo como datos simples, idealmente sin la presencia de un cuadro de entrada ...

Respuestas a la pregunta(2)

Su respuesta a la pregunta