mlewandowski
mlewandowski

Reputation: 802

Spring MVC + Thymeleaf - saving relation @ManyToOne

I am developing an application for managing clients an its machines. I have created all necessary tables, schema, controllers etc.

Client entity has List and Machine has Client relation (bidiretional, non-optional).

The problem I have is related to adding brand new machine to existing Client (providing this one already exists).

So here is some short snip of code:

@Controller
public class MachineController {

...
    @GetMapping("/machines/add/{clientId}")
    public String addMachine(@PathVariable("clientId") int clientId, Model model) throws ClientNotFoundException {
        model.addAttribute("machineTypes", MachineType.values());
        model.addAttribute("machine", new Machine());
        model.addAttribute("client", clientService.find(clientId));
        return "machines/form";
    }
}

    @PostMapping("/machines/save")
    public String saveMachine(@ModelAttribute @Valid Machine machine, BindingResult bindingResult, Model model)
            throws ClientNotFoundException {

        model.addAttribute("machineTypes", MachineType.values());

        int clientId = machine.getClient().getId();
        LOG.debug("ClientId:{}", clientId);
        // Client object is not filled here ! clientId is 0 (new client).
}

The problem is with save function - I don't know how to pass exisitng Client object for Machine object, which is sent by HTTP POST. My controller complains that client is not send and BindingResult is throwing error:

Field error in object 'machine' on field 'client.address.city: rejected value [null]; Field error in object 'machine' on field 'client.address.zipCode: rejected value [null]; Field error in object 'machine' on field 'client.name': rejected value [null];

I am loooking forward to any help.

HTML form presented below:

                                <form class="form-horizontal" th:action="@{/machines/save}" th:object="${machine}" method="post" id="machineForm">
                                <input type="hidden" th:field="*{id}"/>

                                    <!-- Panel for machine -->
                                <div th:class="${#fields.hasErrors('machineType')}? 'form-group has-error has-feedback' : 'form-group'">
                                    <label class="control-label col-sm-4" for="manufacturer">Rodzaj:*</label>
                                    <div class="col-sm-8">

                                        <select th:field="${machine.machineType}" class="form-control" id="machineTypeSelect">
                                            <option value="" disabled="disabled" selected="selected">Wybierz rodzaj</option>
                                            <option th:each="type: ${machineTypes}" th:value="${type.name()}" th:text="${type}" th:attr="data-has-car=${type.hasCar()}"></option>
                                        </select>

                                        <div class="help-block" th:if="${#fields.hasErrors('machineType')}"
                                             th:errors="*{machineType}"></div>
                                    </div>
                                </div>

                                <div th:class="${#fields.hasErrors('manufacturer')}? 'form-group has-error has-feedback' : 'form-group'">
                                    <label class="control-label col-sm-4" for="manufacturer">Producent:*</label>
                                    <div class="col-sm-8">
                                        <input type="text"
                                               class="form-control"
                                               id="manufacturer"
                                               placeholder="Podaj producenta"
                                               th:field="*{manufacturer}" />
                                        <div class="help-block" th:if="${#fields.hasErrors('manufacturer')}"
                                             th:errors="*{manufacturer}"></div>
                                    </div>
                                </div>

                                <div th:class="${#fields.hasErrors('model')}? 'form-group has-error has-feedback' : 'form-group'">
                                    <label class="control-label col-sm-4" for="model">Model:</label>
                                    <div class="col-sm-8">
                                        <input type="text"
                                               class="form-control"
                                               id="model"
                                               placeholder="Podaj model"
                                               th:field="*{model}"/>
                                        <div class="help-block" th:if="${#fields.hasErrors('model')}"
                                             th:errors="*{model}"></div>
                                    </div>
                                </div>

                                <div th:class="${#fields.hasErrors('productionYear')}? 'form-group has-error has-feedback' : 'form-group'">
                                    <label class="control-label col-sm-4" for="productionYear">Rok produkcji:*</label>
                                    <div class="col-sm-8">
                                        <input type="number"
                                               class="form-control"
                                               id="productionYear"
                                               placeholder="Podaj rok produkcji"
                                               th:field="*{productionYear}"/>
                                        <div class="help-block" th:if="${#fields.hasErrors('productionYear')}"
                                             th:errors="*{productionYear}"></div>
                                    </div>
                                </div>

                                <div th:class="${#fields.hasErrors('factoryNo')}? 'form-group has-error has-feedback' : 'form-group'">
                                    <label class="control-label col-sm-4" for="factoryNo">Numer fabryczny:</label>
                                    <div class="col-sm-8">
                                            <input type="number"
                                                   class="form-control"
                                                   id="factoryNo"
                                                   placeholder="Podaj numer fabryczny"
                                                   th:field="*{factoryNo}"/>
                                        <div class="help-block" th:if="${#fields.hasErrors('factoryNo')}"
                                             th:errors="*{factoryNo}"></div>
                                    </div>
                                </div>

                                <div th:class="${#fields.hasErrors('maxLoad')}? 'form-group has-error has-feedback' : 'form-group'">
                                    <label class="control-label col-sm-4" for="maxLoad">Max udżwig:</label>
                                    <div class="col-sm-8">
                                        <div class="input-group">
                                        <input type="number"
                                               class="form-control"
                                               id="maxLoad"
                                               placeholder="Max. udźwig"
                                               aria-describedby="measure"
                                               th:field="*{maxLoad}"/>
                                            <span class="input-group-addon" id="measure">kg</span>
                                        </div>
                                        <div class="help-block" th:if="${#fields.hasErrors('maxLoad')}"
                                             th:errors="*{maxLoad}"></div>
                                    </div>
                                </div>

                                <div th:object="${machine.client}">
                                    <div th:class="${#fields.hasErrors('id')}? 'form-group has-error has-feedback' : 'form-group'">
                                        <label class="control-label col-sm-4" for="clientId">Wybrany klient:</label>
                                        <div class="col-sm-8">
                                            <span class="form-control-static" id="selectedClient">Nie wybrano! Wyszukaj po prawej</span>
                                            <input type="hidden" th:field="${machine.client.id}" id="clientId"  />
                                            <div class="help-block" th:if="${#fields.hasErrors('id')}"
                                                 th:errors="${machine.client.id}"></div>
                                        </div>
                                    </div>
                                </div>

                                <div id="machineCar" th:object="${machine.car}">

                                    <div th:class="${#fields.hasErrors('make')}? 'form-group has-error has-feedback' : 'form-group'">
                                        <label class="control-label col-sm-4" for="carMake">Marka pojazdu:*</label>
                                        <div class="col-sm-8">
                                            <input type="text"
                                                   class="form-control"
                                                   id="carMake"
                                                   placeholder="Podaj markę pojazdu"
                                                   th:field="*{make}"/>
                                            <div class="help-block" th:if="${#fields.hasErrors('make')}"
                                                 th:errors="*{make}"></div>
                                        </div>
                                    </div>

                                    <div th:class="${#fields.hasErrors('vin')}? 'form-group has-error has-feedback' : 'form-group'">
                                        <label class="control-label col-sm-4" for="factoryNo">VIN:</label>
                                        <div class="col-sm-8">
                                            <input type="number"
                                                   class="form-control"
                                                   id="vin"
                                                   placeholder="Podaj numer VIN"
                                                   th:field="*{vin}"/>
                                            <div class="help-block" th:if="${#fields.hasErrors('vin')}"
                                                 th:errors="*{vin}"></div>
                                        </div>
                                    </div>
                                </div>
                                    <div class="form-group">
                                        <div class="col-xs-12">
                                            <button type="submit" class="btn btn-primary">Zapisz dane</button>
                                        </div>
                                    </div>
                                </form>

Upvotes: 2

Views: 2060

Answers (2)

mlewandowski
mlewandowski

Reputation: 802

I resolved my problem. Not sure if it's right solution, but works.

So, simply in Machine entity:

public class Machine {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MACHINE_SEQUENCE")
    private int id;

    // ... 
    @ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE} )
    @JoinColumn(name = "client_id", nullable = false)
    @Valid // REMOVED
    private Client client;

I have removed @Valid annotation on top of Client field and therefore Spring MVC is not validating Thymeleaf form anymore.

Another solution is to include hidden inputs with client details (name, company and child address object) so thymeleaf can transfer complete Object and BindingResult won't complain ...

Upvotes: 0

Rawin Ngamloet
Rawin Ngamloet

Reputation: 11

try to add

    <input type="hidden" name="client.id" value="${client.id}" />

in the form in HTML, those client object with id value will be created and then leave the rest to the repository, it just need id to associate the record.

Upvotes: 1

Related Questions