从 Thymeleaf 获取输入。转化为 ModelAttribute。无法识别 @ModelAttribute bean。无法重定向到新页面

Take input from Thymeleaf. Translate into ModelAttribute. Doesn't recognize the @ModelAttribute bean. Cannot redirect to new page

我正在尝试制作一部新电影。这意味着我想访问“addMovie”URL,以生成一个表单,我可以在其中添加新电影的标题。这个标题应该存储在已经来自 Thymeleaf 表单的 movieDTO 中。然后,当我按下“提交”时,我希望它能将我发送到一个新页面“/saveMovie”,该页面接收到 来自上一个表格的 movieDTO。使用此 movieDTO,我创建了一个新电影,然后“redirect:movie/movies-all”显示所有当前电影(包括添加的一个)。

但是,不会发生这种情况。当我访问 URL localhost:8081/addMovie 时,出现以下错误:

org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'org.thymeleaf.spring5.processor.SpringInputGeneralFieldTagProcessor' (template: "movie/movie-add" - line 18, col 40)

还有这个

Caused by: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'movieDTO' available as request attribute at org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:153) ~[spring-webmvc-5.3.9.jar:5.3.9].

因此,我不明白问题是什么。是不是它没有正确(或根本没有)将信息存储在 movieDTO 中?还是因为bean movieDTO 没有发送到post方法?

项目结构:

控制器:

package movie_API.movie.controllers;

import movie_API.movie.services.MovieService;
import movie_API.movie.services.beans.MovieDTO;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.validation.Valid;
import java.util.Arrays;
import java.util.List;

@Controller
//@RequestMapping("/something") <- if you want a general something before the other resources
public class MovieController {

    private final MovieService movieService;

    public MovieController(MovieService movieService) {
        this.movieService = movieService;
    }  

    @RequestMapping("/addMovie")
    public String add() {
        return "movie/movie-add";
    }

    @RequestMapping(value = "/saveMovie", method = RequestMethod.POST)
    public String create(@ModelAttribute("movieDTO") @Valid MovieDTO movieDTO, Model model, BindingResult result) {
        System.out.println("got into post");
        if (result.hasErrors()) {
            System.out.println("error in result");
            return "movie/movie-add";
        }

        //movieService.saveNewMovie(movieDTO);
        movieService.showMovies(model);
        return "redirect:movie/movies-all";
    }
}

movieDTO+对应的getters和setters:

package movie_API.movie.services.beans;

import java.io.Serializable;

public class MovieDTO implements Serializable {

    private static final long serialVersionUID = -8040351309785589042L;

    private String metascore;
    private String title;
    private String year;
    private String description;
    private String genreId;

    public MovieDTO(String metascore, String title, String year, String description, String genreId) {
        this.metascore = metascore;
        this.title = title;
        this.year = year;
        this.description = description;
        this.genreId = genreId;
    }
}

movie-add.html:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <title>Add Movie</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.4.1/css/all.css">
</head>
<body>
<div class="container my-5">
    <h2 class="mb-5">New Movie</h2>
    <div class="row">
        <div class="col-md-6">
            <form action="#" th:action="@{/saveMovie}" th:object="${movieDTO}" method="post">
                <label>
                    <input type="text" th:field="*{title}" name="title" class="form-control" placeholder="insert string here">
                </label>
                <input type="submit" class="btn btn-primary" value="Submit">
            </form>
        </div>
    </div>
</div>
</body>
</html>

movies-all.html:

<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org/" lang="en">
<head>
    <title> All Movies </title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>


<div class="container">
    <h2>Movies table</h2>
    <p>Here are all the movies we have in the database:</p>
    <table class="table table-bordered">
        <thead>
        <tr>
            <th>ID</th>
            <th>metascore</th>
            <th>title</th>
            <th>year</th>
            <th>description</th>
            <th>genre</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="movie, stat: ${allMovies}">

            <td th:text="${movie.id}"></td>
            <td th:text="${movie.metascore}"></td>
            <td th:text="${movie.title}"></td>
            <td th:text="${movie.year}"></td>
            <td th:text="${movie.description}"></td>
            <td th:text="${allGenres[stat.index]}"></td>

        </tr>
        </tbody>
    </table>
</div>

</body>
</html>

电影服务:

package movie_API.movie.services;

import movie_API.movie.models.Movie;
import movie_API.movie.repositories.GenreRepository;
import movie_API.movie.repositories.MovieRepository;
import movie_API.movie.services.beans.MovieDTO;
import org.springframework.stereotype.Service;
import org.springframework.ui.Model;

import java.util.List;

@Service
public class MovieService {

    private final MovieRepository movieRepository;
    private final GenreRepository genreRepository;
    private List<Movie> movies;
    private Movie movie;

    public MovieService(MovieRepository movieRepository, GenreRepository genreRepository) {
        this.movieRepository = movieRepository;
        this.genreRepository = genreRepository;
    }

    public void showMovies(Model m) {

        m.addAttribute("allMovies", getMovies());
        //System.out.println("done with movies");

        m.addAttribute("allGenres", getGenreNames());
        //System.out.println("done with genres");
    }

    public void showMovieByID(Model m, String id) {
        m.addAttribute("movie", getMovieById(id));
        m.addAttribute("genreName", getGenreNameByMovieId());
    }

    public List<Movie> getMovies() {
        movies = movieRepository.findAll();
        //System.out.println(movies.get(0).toString());
        return movies;
    }

    public Movie getMovieById(String id) {
        this.movie = movieRepository.findById(id).get();
        return movie;
    }

    public List<String> getGenreNames() {
        return movieRepository.getGenreNames(movies, genreRepository);
    }

    public String getGenreNameByMovieId() {
        String genreId = movie.getGenreId();
        return genreRepository.findById(genreId).get().getName();
    }

    public void saveNewMovie(MovieDTO movieDTO) {
        Movie movie = new Movie();

        movie.setTitle(movieDTO.getTitle());
        movie.setYear(movieDTO.getYear());
        movie.setMetascore(movieDTO.getMetascore());
        movie.setDescription(movieDTO.getDescription());
        movie.setGenreId(movieDTO.getGenreId());

        movieRepository.save(movie);
    }

    public void updateMovie(MovieDTO movieDTO, String id) {
        Movie movie = movieRepository.findById(id).get();

        movie.setTitle(movieDTO.getTitle());
        movie.setYear(movieDTO.getYear());
        movie.setMetascore(movieDTO.getMetascore());
        movie.setDescription(movieDTO.getDescription());
        movie.setGenreId(movieDTO.getGenreId());

        movieRepository.save(movie);
    }

    public void deleteMovie(String id) {
        movieRepository.deleteById(id);
    }

}

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>movie_API</groupId>
    <artifactId>movie</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>movie</name>
    <description>Movie REST API</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jersey</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

主要问题:

  1. 错误是什么?为什么不根据用户给定的输入创建 movieDTO? (我只能假设它没有正确创建,因为当我想访问它时它总是空的)
  2. 为什么 redirect 无法识别 movies-all 位置?

对于第一个错误,只需向模型添加一个空的 DTO;

@RequestMapping("/addMovie")
public String add(Model model) {
    model.addAttribute("movieDTO", new MovieDTO());
    return "movie/movie-add";
}

对于第二个错误,删除“重定向:”部分

    movieService.showMovies(model);
    return "movie/movies-all";

然后你会得到你想要的页面;

警告;

请将默认构造函数、setters/getters 和 id 字段添加到 DTO。