In Spring MVC, it is currently possible to map a many-to-one relationship property using the autoGrowNestedPaths feature. However, there is no built-in support for mapping "-to-many" relationship properties when the request parameter names do not match the target property names.
For example, if a Movie entity has a "-to-many" relationship with Actor entities, and the form contains a multi-select dropdown with the name "actors" that allows the user to select multiple actors for the movie, the submitted request parameter values will be in the form of actors=[id1, id2, id3]. In order to map these values to the Movie entity's actors property, a custom Converter must be defined.
It would be helpful if Spring MVC could provide built-in support for mapping "-to-many" relationship properties with property paths in a similar way to how autoGrowNestedPaths works for many-to-one relationships. For example, it would be great if the following syntax could be used in the form field names to indicate that the values should be mapped to a nested property with the given path:
<select name="actors.id" multiple>
<option value="1">Actor 1</option>
<option value="2">Actor 2</option>
<option value="3">Actor 3</option>
</select>
````
This would allow Spring to automatically map the submitted values to the Movie entity's actors property without the need for a custom Converter.
If this enhancement is accepted, I would be happy to help.
Thank you for considering this enhancement request!
**Comment From: sultan**
Here is the Generic Converter i am using to avoid making one per table.
```java
// Copyright (c) 2023 the original author or authors.
package fr.eni.movielibrary.converter;
import java.lang.reflect.*;
import java.util.*;
import java.util.regex.*;
import java.util.stream.*;
import org.springframework.beans.*;
import org.springframework.core.convert.*;
import org.springframework.core.convert.converter.*;
import org.springframework.stereotype.*;
/** @author Souheil SULTAN */
@Component
public class GenericCollectionConverter2 implements ConditionalGenericConverter {
public final static String DEFAULT_ID_NAME = "id";
public final static Pattern ID_ANNOTATION_PATTERN = Pattern.compile( "^(?:javax|jakarta).persistence.Id$" );
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return null;
}
@Override
public boolean matches( TypeDescriptor idType, TypeDescriptor beanType ) {
final var idClass = idType.isArray() ? idType.getElementTypeDescriptor().getType() : idType.getType();
return idClass.equals(String.class) && beanType.isCollection();
}
@Override
public Object convert( Object idValues, TypeDescriptor idType, TypeDescriptor beanType ) {
final var beanClass = beanType.getElementTypeDescriptor().getType();
final var idField = Arrays.stream(beanClass.getDeclaredFields())
.filter( f -> f.getName().equals(DEFAULT_ID_NAME) || Arrays.stream(f.getAnnotations())
.map( a -> a.annotationType().getName() ).anyMatch( ID_ANNOTATION_PATTERN.asMatchPredicate() ))
.findFirst().orElseThrow( () -> new UnknownFormatConversionException("No id field found for: "+beanClass) );
final var idName = idField.getName();
final var values = idType.isArray() ? Arrays.stream((Object[])idValues) : Stream.of(idValues);
return values.map( id -> {
final var bean = new BeanWrapperImpl(beanClass);
bean.setPropertyValue( idName, id );
return bean.getWrappedInstance();
}).collect(Collectors.toList());
}
}
Comment From: sultan
looks implemented now. should we close ?
works with attributes bindings.
<tr>
<th scope="row"><label th:for="name">Nom</label></th>
<td><input th:field="*{name}" class="form-control" /></td>
<td><span th:errorclass="text-danger" th:errors="*{name}"></span></td>
</tr>
<tr>
<th scope="row"><label th:for="crust">Pâte</label></th>
<td>
<select th:field="*{crust}" class="form-control">
<option value=""></option>
<option th:each="crust:${crusts}" th:value="${crust.id}" th:text="${crust.name}"></option>
</select>
</td>
<td><span th:errorclass="text-danger" th:errors="*{crust}"></span></td>
</tr>
<tr>
<th scope="row"><label th:for="toppings">Ingrédients</label></th>
<td>
<select multiple th:field="*{toppings}" class="form-control">
<option th:each="topping:${toppings}" th:value="${topping.id}" th:text="${topping.name}"></option>
</select>
</td>
<td><span th:errorclass="text-danger" th:errors="*{toppings}"></span></td>
</tr>
Comment From: snicoll
Thanks for following-up and sorry we didn't get to it sooner.