Как работать со связанными таблицами в Spring Data JPA

Я готовлю не большой проект в виде панели администратора для небольшого магазина. Есть модели:

  • Product
  • Category

Использую шаблонизатор thymeleaf

Я хочу чтобы в колонке каждого отдельного продукта отображалась его категория.

Я сделал связь продуктов и категорий как многие ко многим. Так как один продукт может иметь несколько категорий, так и в категории может находится множество продуктов. Я заметил, что при создание нового продукта (эта функция уже реализована), создается связывающая таблица, в которой есть колонки с id товара и id категории которую я выбрал при создании товара. Как сделать так чтобы на основе этой таблицы я мог в колонке с товарами (страница сайта) показывать категорию каждого товара. Возможно есть какой-то другой путь осуществления такого функционала? Страница с товарами

Код Product:

 package com.example.cmspanel.models;


import lombok.Data;

import javax.persistence.*;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.List;

@Data
@Entity
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    @NotNull
    @Size(min = 5, message = ("Имя должно быть больше 5 символов."))
    private String name;
    private String description;
    private double price;
    private double productWeight;


    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "prod_cats",
            joinColumns = {@JoinColumn(name = "product_id")},
            inverseJoinColumns = {@JoinColumn(name = "category_products_id")})
    private List<CategoryProduct> categoryProducts = new ArrayList<>();

}

Код Category:

    package com.example.cmspanel.models;

import lombok.Data;


import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.util.ArrayList;
import java.util.List;

@Data
@Entity
public class CategoryProduct {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    @NotBlank(message = "Необходимо ввести название категории.")
    private String name;

    @ManyToMany(fetch = FetchType.EAGER, mappedBy = "categoryProducts")
    private List<Product> products = new ArrayList<>();
}

Репозиторий Product у Category выглядит аналогично:

package com.example.cmspanel.repositories;

import com.example.cmspanel.models.Product;
import org.springframework.data.repository.CrudRepository;

import java.util.List;

public interface ProductRepositories extends CrudRepository<Product, Long> {
    List<Product> findAll();
}

ProductController:

package com.example.cmspanel.controllers;

import com.example.cmspanel.models.CategoryProduct;
import com.example.cmspanel.models.Product;
import com.example.cmspanel.repositories.CategoryRepositories;
import com.example.cmspanel.repositories.ProductRepositories;
import com.example.cmspanel.service.CategoryService;
import com.example.cmspanel.service.ProductService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.support.SessionStatus;


@Data
@Controller
@RequestMapping("/admin-panel")
public class ProductController {

    private final ProductService productService;
    private final CategoryService categoryService;

    @ModelAttribute
    public void addProductToModel(Model model) {
        model.addAttribute("prodlist", productService.listProduct());
    }

    //    Указываем представление
    @GetMapping("/product-list")
    public String showProductPages() {
        return "product-list";
    }

    //    Удаляем товар
    @PostMapping("/product-list/delete/{id}")
    public String deleteProduct(@PathVariable Long id) {
        productService.deleteProduct(id);
        return "redirect:/admin-panel/product-list";
    }

    //    Создание товара
    @GetMapping("/product-list/product-create")
    public String showProdCreatePage() {
        return "product-create";
    }

    @ModelAttribute(name = "newProd")
    public Product getNewProd() {
        return new Product();
    }

    @ModelAttribute(name = "categories")
    public void addCatToModel(Model model) {
        model.addAttribute("categories", categoryService.listCategory());
    }
//    @GetMapping("/product-list/product-create")
//    public void addCatToModel(Model model) {
//        model.addAttribute("categories", categoryService.listCategory());
//    }

    //    Сохранение товара


    @PostMapping("/product-list/product-create")
    public String saveNewProd(Product product, SessionStatus sessionStatus) {
        productService.saveProduct(product);
        sessionStatus.setComplete();
        return "redirect:/admin-panel/product-list";
    }


}

CategoryController:

package com.example.cmspanel.controllers;

import com.example.cmspanel.models.CategoryProduct;
import com.example.cmspanel.service.CategoryService;
import lombok.Data;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;


@Data
@Controller
@RequestMapping("/admin-panel")
public class CategoryController {
    private final CategoryService categoryService;

    @ModelAttribute
    public void addCategoryToModel(Model model) {

        model.addAttribute("category", categoryService.listCategory());
    }

    //    Отображение/удаление категорий
    @GetMapping("/categories")
    public String showHomePage() {
        return "categories";
    }

    @PostMapping("/categories/delete/{id}")
    public String deleteCategories(@PathVariable Long id) {
        categoryService.deleteCategory(id);
        return "redirect:/admin-panel/categories";
    }

    //    Создание категории
    @GetMapping("/categories/create")
    public String showCreatePage() {
        return "create-category";
    }

    @ModelAttribute(name = "categoryProductName")
    public CategoryProduct addCategoryProduct() {
        return new CategoryProduct();
    }

    @PostMapping("/categories/create")
    public String createCategory(CategoryProduct categoryProduct) {

        categoryService.saveCategory(categoryProduct);
        return ("redirect:/admin-panel/categories");
    }
}

Представление(Thymeleaf) страницы с отображением всех товаров:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Панель управления</title>
    <link th:href="@{/css/style.css}" rel="stylesheet"/>
    <!--    <link rel="stylesheet" href="../css/style.css">-->
    <!--    <link rel="stylesheet" href="/css/normalize.css">-->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6" crossorigin="anonymous">
</head>

<body>
<header class="header header-pad" th:insert="fragments/header :: header">
</header>
<main>
    <div class="wraper flex-1-0-auto">
        <div class="container-fluid flex-1-0-auto">
            <div class="row flex-1-0-auto">
                <div class="col-xxl-2 no-padding-no-margin" th:insert="fragments/sidebar :: sidebar">
                </div>
                <!-- Окно -->
                <div class="col-xxl-8">
                    <div class="row">
                        <div class="col-xxl-12">
                            <div class="title-window-wraper">
                                <p class="title-window">Все продукты</p>
                            </div>
                            <div class="col-xxl-12">
                                <div class="but-window-wraper">
                                    <a class="a-but-win" th:href="@{/admin-panel/product-list/product-create}">Создать продукт</a>
                                </div>
                            </div>

                            <tr th:each="prod : ${prodlist}">
                                <form method="POST">
                                    <!-- Колонка товара -->
                                    <div class="col-xxl-12">
                                        <div class="product-column-wraper">
                                            <div class="col-xxl-2">
                                                <div class="product-img-wraper">
                                                    <img class="product-img" th:src="@{/img/product/cola.png}" alt="">
                                                </div>
                                            </div>
                                            <div class="col-xxl-6">
                                                <div class="description-product-wraper">
                                                    <p class="title-product" th:text="${prod.name}">Название
                                                        продукта</p>
                                                    <p class="description-product" th:text="${prod.description}">
                                                        Описание продукта, текст и все что с ним связано</p>
                                                    <div class="col-xxl-12">
                                                        <div class="cat-and-price-product-wraper">
                                                            <p class="cat-text-1">Категория: </p>
                                                            <p class="cat-text-2" >
                                                                Напитки</p>
                                                            <p class="price-text-1">Цена: </p>
                                                            <p class="price-text-2" th:text="${prod.price}">1800р</p>
                                                            Руб.
                                                        </div>
                                                    </div>
                                                </div>
                                            </div>
                                            <div class="col-xxl-4">
                                                <div class="categoty-count-wraper-2 product-count-wraper">
                                                    <a class="a-but-win red-but-prod" href="">Редактировать</a>
                                                    <p class="palka-p">/</p>
                                                    <button class="a-but-win but-del-win"
                                                            th:formaction="@{/admin-panel/product-list/delete/{id}(id=${prod.id})}">
                                                        Удалить
                                                    </button>
                                                </div>
                                            </div>
                                        </div>
                                    </div>
                                    <!-- \\\\\\Колонка товара -->
                                </form>
                            </tr>


                        </div>
                    </div>

                </div>

</main>

<!--Footer-->
<div th:insert="fragments/footer :: footer"></div>

<script src="js/js.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf"
        crossorigin="anonymous"></script>
</body>

</html>

Ответы (1 шт):

Автор решения: Roman-Stop RU aggression in UA

Идея решения такая:

     <p class="cat-text-2" >
       <ul class="categories">
         <li class="category-name" th:each="category: ${prod.categoryProducts}">
            <span th:text="${category.name}">Имя категории<span>
         </li>
       </ul>
     </p>
→ Ссылка