我正在编写简单的博客网络应用程序。 我想要做的是从数据库加载数据,将其传递给表单输入(不是全部,只有我想要编辑的那些)编辑它们并将它们作为更新传递给数据库。
我正在从数据库加载我的帖子,数据正确传递给编辑表单,甚至deugger看到Post已经设置了作者,但是在提交此表单后,我得到错误,当user_id为null时我无法保存实体。为什么呢?
我尝试了什么?
我的代码:
发布模型:
package io.gromo13.personalBlog.model;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
@Entity
@Table(name = "posts")
public class Post {
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "title", nullable = false)
@NotNull
@Size(min = 3, max = 50)
private String title;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User author;
@Column(name = "date", nullable = false)
private Date creationDate;
@Column(name = "contents", nullable = false)
@NotNull
@Size(min = 3, max = 500)
private String contents;
public Post(String title, String contents, User author) {
this();
this.title = title;
this.contents = contents;
this.author = author;
}
public Post() {
creationDate = new Date();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public User getAuthor() {
return author;
}
public void setAuthor(User author) {
this.author = author;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public String getContents() {
return contents;
}
public void setContents(String contents) {
this.contents = contents;
}
}
用户模型:
package io.gromo13.personalBlog.model;
import org.hibernate.validator.constraints.Email;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
@Entity
@Table(name="users")
public class User {
@Id
@Column(name = "id", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username", nullable = false)
@NotNull
@Size(min=3, max=20)
private String username;
@Column(name = "password", nullable = false)
@NotNull
@Size(min=5, max=20)
private String password;
@Column(name = "email", nullable = false)
@NotNull
@Email
private String email;
@ManyToOne
@JoinColumn(name = "role_id", nullable = false)
@NotNull
private Role role;
@OneToMany(mappedBy = "author")
private List<Post> posts;
public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
public User() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public List<Post> getPosts() {
return posts;
}
public void setPosts(List<Post> posts) {
this.posts = posts;
}
}
邮政控制人:
package io.gromo13.personalBlog.controller;
import io.gromo13.personalBlog.model.User;
import io.gromo13.personalBlog.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import io.gromo13.personalBlog.model.Post;
import io.gromo13.personalBlog.service.PostService;
import javax.validation.Valid;
@Controller
@RequestMapping("/post")
public class PostController {
@Autowired
private PostService postService;
@Autowired
private UserService userService;
public void setPostService(PostService postService) {
this.postService = postService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
@GetMapping("/add")
public String addPost(Model model) {
model.addAttribute("post", new Post());
return "/post/add";
}
@PostMapping("/add")
public String addPostSubmit(@Valid Post post, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "/post/add";
}
User user = getLoggedInUser();
post.setAuthor(user);
postService.add(post);
return "redirect:/admin/posts";
}
private User getLoggedInUser() {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof org.springframework.security.core.userdetails.User) {
String username = ((org.springframework.security.core.userdetails.User) principal).getUsername();
User user = userService.findByUsername(username);
return user;
}
return null;
}
@GetMapping("/edit/{id}")
public String editPost(@PathVariable Long id, Model model) {
Post post = postService.getEager(id);
model.addAttribute("post", post);
return "/post/edit";
}
@PostMapping("/edit/{id}")
public String editPostSubmit(@ModelAttribute @Valid Post post, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "/post/edit";
}
postService.edit(post);
return "redirect:/admin/posts";
}
}
发布存储库:
package io.gromo13.personalBlog.repository;
import io.gromo13.personalBlog.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface PostRepository extends JpaRepository<Post, Long> {
// @Query("SELECT post FROM Post post INNER JOIN FETCH post.author AS author where post.id = :id and post.author = author.id")
@Query("SELECT post from Post post inner join fetch post.author as author where post.id = :id and post.author = author.id")
Post findWithAuthorById(@Param("id") Long id);
}
邮政服务:
package io.gromo13.personalBlog.service;
import io.gromo13.personalBlog.repository.PostRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import io.gromo13.personalBlog.model.Post;
import java.util.List;
@Service
public class PostService {
@Autowired
private PostRepository postRepository;
public void setPostRepository(PostRepository postRepository) {
this.postRepository = postRepository;
}
public void add(Post post) {
postRepository.save(post);
}
public Post get(Long id) {
return postRepository.findOne(id);
}
public Post getEager(Long id) {
return postRepository.findWithAuthorById(id);
}
public List<Post> getAll() {
return (List<Post>) postRepository.findAll();
}
public void edit(Post post) {
postRepository.save(post);
}
public void delete(Long id) {
postRepository.delete(id);
}
public List<Post> getLatestPosts(int id, int count) {
//TODO implement
return null;
}
}
修改表单视图:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<div th:replace="/fragments/header"> </div>
<div>
<h3>Editing post</h3>
<form id="editPostForm" action="#" th:action="@{/post/edit/} + ${post.id}" th:object="${post}" method="post">
<!--<p>Author: <input type="text" th:field="*{author.username}" readonly="readonly" /></p>-->
<p>Author: <span th:text="*{author.username}"></span></p>
<p>Creation date: <span th:text="*{creationDate}"></span></p>
<p>Title: <input type="text" th:field="*{title}" /></p>
<p th:if="${#fields.hasErrors('title')}" th:errors="*{title}"></p>
<p>Contents:</p>
<textarea th:form="editPostForm" rows="5" cols="30" th:field="*{contents}"></textarea>
<p th:if="${#fields.hasErrors('contents')}" th:errors="*{contents}"></p>
<p><input type="submit" value="Submit" /><input type="reset" value="Reset" /></p>
</form>
</div>
<div th:replace="/fragments/footer"> </div>
</body>
</html>
浏览器中返回错误:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed Nov 22 01:42:42 CET 2017
There was an unexpected error (type=Internal Server Error, status=500).
could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
来自IDE的异常+部分数据库日志:
Hibernate: update posts set user_id=?, contents=?, date=?, title=? where id=?
2017-11-22 01:42:42.920 WARN 5192 --- [nio-8080-exec-7] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1048, SQLState: 23000
2017-11-22 01:42:42.920 ERROR 5192 --- [nio-8080-exec-7] o.h.engine.jdbc.spi.SqlExceptionHelper : Column 'user_id' cannot be null
2017-11-22 01:42:42.922 INFO 5192 --- [nio-8080-exec-7] o.h.e.j.b.internal.AbstractBatchImpl : HHH000010: On release of batch it still contained JDBC statements
2017-11-22 01:42:42.966 ERROR 5192 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'user_id' cannot be null
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_131]
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_131]
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_131]
at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_131]
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.Util.getInstance(Util.java:408) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:935) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3973) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3909) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2527) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2680) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2487) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1858) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2079) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2013) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:5104) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1998) ~[mysql-connector-java-5.1.44.jar:5.1.44]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_131]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_131]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_131]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_131]
at org.apache.tomcat.jdbc.pool.StatementFacade$StatementProxy.invoke(StatementFacade.java:114) ~[tomcat-jdbc-8.5.23.jar:na]
at com.sun.proxy.$Proxy112.executeUpdate(Unknown Source) ~[na:na]
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3134) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3013) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3393) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:145) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:582) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:456) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1282) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:465) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:2963) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2339) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final]
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final]
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517) ~[spring-orm-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761) ~[spring-tx-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730) ~[spring-tx-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504) ~[spring-tx-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292) ~[spring-tx-4.3.12.RELEASE.jar:4.3.12.RELEASE]
答案 0 :(得分:1)
您的User
是null
,或者它未被保留,只留下id
null
。
确保您的User
来自数据库,在存储Post
之前将其存储在数据库中,或确保将Post
cascades存储到{{1}实体。
根据您自己的答案和评论中的调试信息进行更新
看起来您正在尝试将当前用户保留在会话中并将其用作起点,然后从请求中包含的字段进行更新。
错误在于模型默认只有请求范围。
一旦邮件请求到达,您在get请求期间放置的User
就会消失。
您应该能够通过向控制器添加Post
来修复该行为,这会使属性会话成为范围,因此当您返回时它仍然存在。
您可以详细了解@SessionAttributes("post")
如何运作以及如何在这个出色的答案中使用它:https://stackoverflow.com/a/26916920/66686
答案 1 :(得分:0)
在使用调试器分析程序流时,我推断提交表单会创建整个新的Post对象,其中包含来自视图表单输入字段的数据。
我是怎么找到的?创建日期始终设置为编辑时间。传递给提交表单的编辑帖子也只设置了标题和内容,这是我表单中唯一的输入字段。
如果传递给提交方法的帖子是全新的对象,则无法接收作者的ID,因为我只是在添加帖子表单时设置它,我收到活动登录用户并将其设置为帖子的作者。
我找到了解决此问题的方法。
@GetMapping("/edit/{id}")
public String editPost(@PathVariable Long id, Model model) {
Post post = postService.getEager(id);
model.addAttribute("post", post);
return "/post/edit";
}
@PostMapping("/edit/{id}")
public String editPostSubmit(@ModelAttribute("post") @Valid Post post, @PathVariable Long id, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "/post/edit";
}
Post editedPost = postService.getEager(id);
editedPost.setTitle(post.getTitle());
editedPost.setContents(post.getContents());
postService.edit(editedPost);
return "redirect:/admin/posts";
}
我在这里做的是接收来自Post对象的编辑数据,这是Post的Title和Contents,我从路径变量接收原始Post的ID。然后我从数据库再次加载此Post并在表单中的那些已编辑变量上设置其标题和内容字段。 这意味着Post设置了作者的ID,创建日期保持不变,并且更改的只是我想要编辑的字段:标题和内容。
也许这不是优雅的解决方法,它应该可以工作而无需从数据库加载Post,只需通过接收此帖子 观点,但它没有奏效。
我正在寻找真正解决这个问题,但现在它工作正常。