Spring Boot + Thymeleaf: th не может разрешить выражение Spring EL

Я использую пружинный ботинок + тимелеф + neo4j. Все работает нормально, за исключением того, что тимелеаф не может разрешить некоторые атрибуты переменной 'product', используемые в блоке th: each в шаблоне product_grid.html, который включает th: src = "$ {product.URL} ", th: text =" $ {Product.title} "и выражение th: action =" @ {/ product / ($ {Product.getId ()})} "в теге формы. Th: text = "$ {Product.Price}" работает. Когда я проверяю код, созданный в браузере, тег src пуст (src: ""), текстовый атрибут, содержащий тег заголовка, не отображается в браузере. Действие th: работает нормально, но когда я нажимаю кнопку, определенную внутри формы, URL-адрес меняется на http: // localhost: 8080 / product /? btn = Просмотр + Продукт вместо следующего кода, который отображается в консоли браузера http: // локальный: 8080 / продукт /? 1

Примечание: я пытаюсь получить URL-адрес изображения из поля, которое хранится в базе данных neo4j. Каталог проекта: образ каталога проекта

Шаблон: product_grid.html

<html xmlns:th = "http://www.thymeleaf.org" >
<head>
    <meta charset = "utf-8" />
    <meta http-equiv = "X-UA-Compatible" content = "IE=edge">
    <title>Products</title>
    <meta name = "viewport" content = "width=device-width, initial-scale=1">
    <script src = "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.bundle.min.js" integrity = "sha384-feJI7QwhOS+hwpX2zkaeJQjeiwlhOP+SdQDqhgvvo1DsjtiSQByFdThsxO669S2D"
            crossorigin = "anonymous"></script>
    <script src = "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity = "sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin = "anonymous"></script>
    <link href = "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel = "stylesheet" integrity = "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
          crossorigin = "anonymous">
</head>

<body>


<nav class = "navbar navbar-expand-lg navbar-dark bg-dark">
    <a class = "navbar-brand" href = "#">Grada</a>
    <button class = "navbar-toggler" type = "button" data-toggle = "collapse" data-target = "#navbarSupportedContent" aria-controls = "navbarSupportedContent"
            aria-expanded = "false" aria-label = "Toggle navigation">
        <span class = "navbar-toggler-icon"></span>
    </button>

    <div class = "collapse navbar-collapse" id = "navbarSupportedContent">
        <ul class = "navbar-nav mr-auto">
            <li class = "nav-item ">
                <a class = "nav-link" href = "#">Home
                    <span class = "sr-only">(current)</span>
                </a>
            </li>
            <li class = "nav-item">
                <a class = "nav-link">My Best Products</a>
            </li>

            <li class = "nav-item">
                <a class = "nav-link" th:href = "@{/login}">Login</a>
            </li>
        </ul>
        <form class = "form-inline my-2 my-lg-0">
            <input class = "form-control mr-sm-2" type = "search" placeholder = "Search" aria-label = "Search">
            <a class = "btn btn-outline-success my-2 my-sm-0" href = "file:///home/madhav/SPM/Grada/public_html/product.html">Search</a>
        </form>
    </div>
</nav>
<div class = "container text-center">
    <div class = "row">
        <div th:each = "Product:${products}" class = "col-lg-3 col-sm-12 col-md-6 my-2 p-auto">
            <div class = "card">
                <div class = "card-body">
                    <img src = "http://via.placeholder.com/150x150/888/111" th:src = "${Product.URL}" alt = "img" class = "card-img-top img-thumbnail img-fluid">
                    <div class = "card-title lead" th:text = "${Product.title}">Some product name</div>
                    <div class = "card-text">Price: &#8377;<span th:text = "${Product.Price}">400</span></div>
                </div>
                <form method = "GET" action = "/" th:action = "@{/product/(${Product.getId()})}">
                    <input type = "submit" name = "btn" class = "form-control btn btn-primary" value = "View Product">
                    <input type = "submit" name = "btn" class = "form-control btn btn-primary" value = "Add to Cart">
                </form>
            </div>
        </div>
    </div>
</div>

</body>
</html>`

Модель продукта: Product.html

package com.grada.ecommerce.Models;


import com.grada.ecommerce.Models.Seller;
import org.neo4j.ogm.annotation.*;


import java.util.HashSet;
import java.util.Set;


@NodeEntity(label = "Product")
public class Product
{
    public  Product()
    {
    }

    public Product(String title, Double price, int quantity, float rating, String description, String url, String company)
    {
        this.title  = title;
        this.Rating = rating;
        this.Description = description;
        this.Price = price;
        this.Quantity = quantity;
        this.URL = url;
        this.Company = company;
    }

    @Id
    @GeneratedValue
    private Long id;

    @Property(name = "title")
    public String title;
    
    @Property(name = "Rating")
    public float Rating;
    @Property(name = "Description")
    public String Description;
    @Property(name = "Price")
    public Double Price;
    @Property(name = "Quantity")
    public int Quantity;
    @Property(name = "Company")
    public String Company;
    @Property(name = "URL")
    public String URL;


    @Override
    public String toString()
    {
        return this.title;
    }

    public Long getId() {
        return id;
    }

    public String getTitle()
    {
        return title;
    }

   @Relationship(type = "Sells", direction = Relationship.INCOMING)
   public Seller Seller;

}
ProductController.java

package com.grada.ecommerce.Controllers;

import com.grada.ecommerce.Models.Product;
import com.grada.ecommerce.Models.Seller;
import com.grada.ecommerce.Services.ProductService;
import com.grada.ecommerce.Services.SellerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class ProductController
{

    final ProductService productService;
    final SellerService sellerService;

    @Autowired
    public ProductController(ProductService productService, SellerService sellerService)
    {
        this.productService = productService;
        this.sellerService = sellerService;
    }

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String Index(Model model)
    {
        Iterable<Product> products  = productService.products();
        model.addAttribute("products", products);
        return "product_grid";
    }

    @RequestMapping(value = "/product", method = RequestMethod.GET)
    public String ShowProduct(@RequestParam(value = "id") Long id, Model model)
    {
        Product product = productService.findProductByID(id);
        if (product == null)
            return "redirect:/";
        model.addAttribute("product", product);
        return "productid";
    }

    @RequestMapping(value = "/add")
    public String AddProduct(Model model)
    {
        model.addAttribute("product", new Product());
        return "add";
    }

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String AddProduct(@ModelAttribute Product product)
    {
        productService.addProduct(product);
        return "redirect:/";
    }

    @RequestMapping(value = "/delete", method = RequestMethod.GET)
    public String DeleteProduct(Model model)
    {
        model.addAttribute("product", new Product());
        return "delete";
    }

    @RequestMapping(value = "/delete", method = RequestMethod.POST)
    public String DeleteProduct(@ModelAttribute Product product)
    {
        productService.deleteProduct(product);
        return "redirect:/";
    }

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String LoginPage(Model model)
    {
        return "login";
    }

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String Authenticate(Model model, String username, String password)
    {
        if (username.equalsIgnoreCase("[email protected]") && password.equals("fakeseller"))
        {
            Iterable<Product> products  = productService.products();
            model.addAttribute("products", products);
            return "loggedin";
        }

        else
            return "redirect:/login";
    }

    @RequestMapping(value = "/policy", method = RequestMethod.GET)
    public String PolicyPage()
    {
        return "policies";
    }
}

Вы пробовали изменить регистр на строчные? Или еще лучше, вместо Product назовите ее oneProduct или item, чтобы переменная была уникальной и более удобной для чтения.

vphilipnyc 16.04.2018 00:09

Кроме того, по какой причине вы используете method = "GET" вместо POST?

vphilipnyc 16.04.2018 00:12

Кроме того, ваш пользовательский интерфейс будет выглядеть странно - вы будете создавать новый столбец для каждого продукта, если внимательно присмотритесь.

vphilipnyc 16.04.2018 00:17

Хорошо, я изменю его на нижний регистр. Нет особой причины для использования GET, но должно ли это иметь значение? Что касается UI, я просто пытался распечатать информацию о каждом продукте, который есть в БД, на странице.

Amritanshu Amrit 17.04.2018 00:58

Да, использование GET vs POST имеет значение. Если вы отправляете данные, вы захотите использовать POST: stackoverflow.com/questions/504947/…

vphilipnyc 17.04.2018 04:12

Вы также можете использовать сокращенные обозначения @GetMapping и @PostMapping в вашем контроллере. И распечатайте значение productService.products();, чтобы убедиться, что вы добавляете товары нормально.

vphilipnyc 17.04.2018 04:15

Есть ли в вашем классе Product установщики для значений, которые вам не хватает? Если это проблема, я отправлю ответ

vphilipnyc 17.04.2018 04:19
0
7
1 011
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Добро пожаловать в SO.

Включите методы setX для ваших переменных в класс Product. Тимелисту они нужны для связывания.

ИМО, отличный способ сделать это - использовать Project Lombok и просто аннотировать свой класс с помощью @Data. Тогда вам даже не нужно будет указывать геттеры или сеттеры (или ваш toString()) вообще.

Используйте строчные буквы для своих переменных, поскольку по соглашению переменные с заглавной первой буквой относятся к классу, а не к переменной экземпляра.

Как уже упоминалось, используйте в форме POST вместо GET, поскольку вы отправляете данные.

Используйте сокращения @GetMapping и @PostMapping, чтобы облегчить чтение.

Когда я изменил метод с GET на POST, он сработал! Спасибо! Но согласно предоставленной вами ссылке GET используется для извлечения данных, т.е. запрос, не так ли? POST будет использоваться, когда я захочу изменить некоторые данные. Здесь я просто отправляю товар и получаю подробную информацию о нем. Итак, почему использование GET создает конкретную проблему, которую я получаю?

Amritanshu Amrit 17.04.2018 07:05

Было бы слишком долго обсуждать это в комментарии, но в вашем случае ваш View Product можно было бы заменить простым тегом привязки. Состояние системы не меняется, и я не вижу смысла даже создавать из нее форму. Если вы добавляете в корзину, за кулисами обычно происходит много вещей, вы хотите, чтобы это было безопасно, и т. д. У вас тоже много других проблем, поэтому не стоит их изучать. пока вы не разберетесь с ними. Основная причина заключается в том, какой метод вы на самом деле вызываете, в зависимости от GET или POST.

vphilipnyc 17.04.2018 20:02

Другие вопросы по теме