I am writing simple blog web app. What I want to do is load data from database, pass it to view into form inputs(not all, only those I want to be editable) edit them and pass them to database as update.
I am loading my Post from database, data correctly passes to edit form, even deugger see that Post has author setted, but after submitting this form, I got error that I cannot save entity while user_id is null. Why?
What I tried?
- ommitting default hibernate's lazyFetching by writing my own fetching query in repository
- adding non-editable text-field with athuor's username to edit form
My code:
Post model:
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;
}
}
User model:
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;
}
}
Post controller:
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";
}
}
Post repository:
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);
}
Post service:
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;
}
}
Edit form view:
<!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>
Error returned in browser:
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
Exception + part of database logs from 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]