Problem z Walidacją ThymeLeaf

0

Cześć,

mam problem z walidacją.

Sprawdzałem inne wątki, na podstawie tego link walidacja mi wychodzi i jest tak jak powinno być.

Mianowicie, mam taką klasę Taco:

package com.example.tacocloud;


import lombok.Data;

import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

import java.util.List;

@Data
public class Taco {

    @NotNull
    @Size(min = 5, message = "Nazwa musi składać się z przynajmniej pięciu znaków.")
    private String name;
    @NotNull
    @Size(min = 1, message = "Musisz wybrać przynajmniej jeden składnik.")
    private List<String> ingredients;


}


taką klasę OrderController:

package com.example.tacocloud.web;

import com.example.tacocloud.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import jakarta.validation.Valid;

@Slf4j
@Controller
@RequestMapping("/orders")

public class OrderController {

    @GetMapping("/current")
    public String orderForm(Model model) {
        model.addAttribute("order", new Order());
        return "orderForm";
    }

    @PostMapping
    public String processOrder(@Valid Order order, Errors errors) {
        for (ObjectError error : errors.getAllErrors()) {
            log.error("Validation error: " + error.getDefaultMessage());
        }
        if (errors.hasErrors()) {
            return "orderForm";
        }
        log.info("Zamówienie zostało złozone: " + order);
        return "redirect:/";
    }
}


i taki plik design.html:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Zjedz z Łukim</title>
    <link rel="stylesheet" th:href="@{../styles.css}"/>
</head>
<body>
<div class="container">
    <div class="left-column">
        <h1>Przygotuj własne Taco!</h1>
        <form method="POST" th:object="${design}">
            <div class="grid">
                <div class="ingredient-group" id="wraps">
                    <h3>Wybierz rodzaj mąki:</h3>
                    <div th:each="ingredient : ${wrap}">
                        <input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
                        <span th:text="${ingredient.name}">SKŁADNIK</span><br/>
                    </div>
                </div>

                <div class="ingredient-group" id="proteins">
                    <h3>Wybierz mięso:</h3>
                    <div th:each="ingredient : ${protein}">
                        <input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
                        <span th:text="${ingredient.name}">SKŁADNIK</span><br/>
                    </div>
                </div>

                <div class="ingredient-group" id="cheeses">
                    <h3>Wybierz sery:</h3>
                    <div th:each="ingredient : ${cheese}">
                        <input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
                        <span th:text="${ingredient.name}">SKŁADNIK</span><br/>
                    </div>
                </div>

                <div class="ingredient-group" id="veggies">
                    <h3>Wybierz warzywa:</h3>
                    <div th:each="ingredient : ${veggies}">
                        <input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
                        <span th:text="${ingredient.name}">SKŁADNIK</span><br/>
                    </div>
                </div>

                <div class="ingredient-group" id="sauces">
                    <h3>Wybierz sosy:</h3>
                    <div th:each="ingredient : ${sauce}">
                        <input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
                        <span th:text="${ingredient.name}">SKŁADNIK</span><br/>
                    </div>
                </div>
            </div>

            <div>
                <h3>Nadaj nazwę przygotowanemu TACO:</h3>
                <input type="text" th:field="*{name}"/>
                <span class="validationError"
                      th:if="${#fields.hasErrors('name')}"
                      th:errors="*{name}">Nazwa Taco powinna składać się z minimum 5 znaków.</span>
                <br/>

                <button>Wyślij swoje zamówienie</button>
            </div>
        </form>
    </div>
    <div class="right-column">
        <img class="logo" th:src="@{../TacoCloud.png}" alt="Logo"/>
    </div>
</div>
</body>
</html>


Mimo tak skonstruowanego kodu po przejściu na http://localhost:8080/design
i nie wprowadzeniu żadnych danych strona przechodzi do /orders/current, a na konsoli dostaję zwrot:

Przetwarzanie projektu taco: Taco(name=, ingredients=null)

Bardzo proszę o pomoc 😀

0

Podaj proszę jeszcze wersje springa.

0
Majksu napisał(a):

Podaj proszę jeszcze wersje springa.

3.2.2

0

Mimo tak skonstruowanego kodu po przejściu na http://localhost:8080/design

no dobra, a gdzie jest endpoint "/design" zdefiniowany?
jak stukasz na "/orders" jest to samo? a może powinieneś stuknąć na "/orders/design" i zapomniałeś dodać "/design" w PostMapping?

bo tylko przeleciałem to wzrokiem, nie wiem jak masz ustawione propertisy albo jakieś inne mappingi. Ogólnie po strzeleniu na /orders, powinna być zwrotka jako design.html. Takie jest założenie?

0
 package com.example.tacocloud.web;

import com.example.tacocloud.data.IngredientRepository;
import com.example.tacocloud.data.TacoRepository;
import com.example.tacocloud.model.Ingredient;
import com.example.tacocloud.model.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import com.example.tacocloud.model.Ingredient.Type;
import com.example.tacocloud.model.Taco;

import javax.validation.Valid;


@Slf4j
@Controller
@RequestMapping("/design")
@SessionAttributes("order")

public class DesignTacoController {

    private final IngredientRepository ingredientRepository;
    private TacoRepository designRepo;

    @Autowired
    public DesignTacoController(IngredientRepository ingredientRepository, TacoRepository designRepo) {
        this.ingredientRepository = ingredientRepository;
        this.designRepo = designRepo;
    }

    @ModelAttribute(name = "order")
    public Order order() {
        return new Order();
    }

    @ModelAttribute(name = "design")
    public Taco taco() {
        return new Taco();
    }


    @GetMapping
    public String showDesignFrom(Model model) {
        List<Ingredient> ingredients = new ArrayList<>();
        ingredientRepository.findAll().forEach(ingredients::add);

        Type[] types = Ingredient.Type.values();
        for (Type type : types) {
            model.addAttribute(type.toString().toLowerCase(),
                    filterByType(ingredients, type));
        }
        model.addAttribute("design", new Taco());
        return "design";
    }

    @PostMapping
    public String processDesign(@Valid Taco design, Errors errors, @ModelAttribute Order order) {
        if (errors.hasErrors()) {
            return "design";
        }

        Taco saved = designRepo.save(design);
        order.addDesign(saved);

        log.info("Przetwarzanie projektu taco: " + design);

        return "redirect:/orders/current";
    }

    private List<Ingredient> filterByType(
            List<Ingredient> ingredients, Type type) {
        return ingredients.stream().filter(x -> x.getType().equals(type)).collect(Collectors.toList());
    }
}



tutaj DesignTacoController

0
    @GetMapping
    public String showDesignFrom(Model model) {
        List<Ingredient> ingredients = new ArrayList<>();
        ingredientRepository.findAll().forEach(ingredients::add);

        Type[] types = Ingredient.Type.values();
        for (Type type : types) {
            model.addAttribute(type.toString().toLowerCase(),
                    filterByType(ingredients, type));
        }
        model.addAttribute("design", new Taco());
        return "design";
    }

wygląda na to, że new Taco() jaki dodajesz tu: model.addAttribute("design", new Taco()); jest po prostu pusty, więc dostajesz prawidłowy rezultat Taco(name=, ingredients=null) 🙃
ale proponuję się zadebugować tutaj i sprawdzić.

Kolejna sprawa, to ingredientRepository.findAll().forEach(ingredients::add); - staraj się nie używać forEach wcale. To jest przydatne jak na biegu się robi System.out.print();. Zdecydowanie lepiej użyć .map()

0
trojanus__ napisał(a):
    @GetMapping
    public String showDesignFrom(Model model) {
        List<Ingredient> ingredients = new ArrayList<>();
        ingredientRepository.findAll().forEach(ingredients::add);

        Type[] types = Ingredient.Type.values();
        for (Type type : types) {
            model.addAttribute(type.toString().toLowerCase(),
                    filterByType(ingredients, type));
        }
        model.addAttribute("design", new Taco());
        return "design";
    }

wygląda na to, że new Taco() jaki dodajesz tu: model.addAttribute("design", new Taco()); jest po prostu pusty, więc dostajesz prawidłowy rezultat Taco(name=, ingredients=null) 🙃
ale proponuję się zadebugować tutaj i sprawdzić.

Kolejna sprawa, to ingredientRepository.findAll().forEach(ingredients::add); - staraj się nie używać forEach wcale. To jest przydatne jak na biegu się robi System.out.print();. Zdecydowanie lepiej użyć .map()

Dziękuję za ponowne włączenie się do dyskusji, co do model.addAttribute("design", new Taco()); wydaje mi się, że gdyby rzeczywiście to było problemem, wówczas przy prawidłowym wpisaniu danych obiekt miałby same null'owe wartości, a tak nie jest:

Próba przy wypełnieniu formularza na stronie.

Zamówienie zostało złozone: Order(id=1, placedAt=Sun Mar 03 12:41:23 CET 2024, deliveryName=Imie i nazwisko, deliveryStreet=Adres, deliveryCity=Miasto, deliveryState=US, deliveryZip=11-111, ccNumber=1111, ccExpiration=111, ccCVV=111, tacos=[Taco(id=1, createdAt=Sun Mar 03 12:40:50 CET 2024, name=nr 1, ingredients=[COTO, GRBF, CHED, TMTO, SLSA])])

W bazie danych również zapisuje się prawidłowo.

Dziękuję za informacje odnośnie foreach , mógłbyś jednak wytłumaczyć dlaczego starać się go nie używać?

0

link do doku: https://docs.oracle.com/javase/tutorial/collections/streams/index.html#:~:text=for%20more%20information.-,Differences%20Between%20Aggregate%20Operations%20and%20Iterators,-Aggregate%20operations%2C%20like

ogólnie rzecz ujmując, forEach() wewnętrznie może (choć nie musi) wykorzystać zrównoleglony sposób iteracji. Czyli nie poleci po streamie sekwencyjnie, tylko np. podzieli sobie stream na 2 lub 3 i w ten sposób będzie iterował. Efektem ubocznym tej operacji może być, że docelowa Lista do której dodajesz będzie miała elementy bez zachowanej pierwotnej kolejności. W skrócie pomiesza kolejność.

1 użytkowników online, w tym zalogowanych: 0, gości: 1