从 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>
主要问题:
- 错误是什么?为什么不根据用户给定的输入创建 movieDTO? (我只能假设它没有正确创建,因为当我想访问它时它总是空的)
- 为什么 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。
我正在尝试制作一部新电影。这意味着我想访问“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>
主要问题:
- 错误是什么?为什么不根据用户给定的输入创建 movieDTO? (我只能假设它没有正确创建,因为当我想访问它时它总是空的)
- 为什么 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。