Error - not-null property references a null or transient value

Viewed 3232

I'm a newbie in Spring. I make an API that based on relationship between two tables, for that use a OneToMany annotation & for API test I've postman. My aim is to save requested data in two seperate entities which are mentioned hereunder. When I try to post a data in postman: 1-3 fields are related to Post entity whereas text field belong to Comment entity (comments is joining field)

{
"title": "Post1",
"description": "Post 1 description",
"content": "Post 1 content",
"comments": [
    {
        "text": "Java best selling book"
    },
    {
        "text": "Exploring spring boot"
    }
]

}

I get an error as follows:

Hibernate: insert into posts (content, description, title) values (?, ?, ?)
2021-09-01 11:55:09.786 TRACE 9884 --- [nio-8089-exec-2] o.h.type.descriptor.sql.BasicBinder      
: binding parameter [1] as [VARCHAR] - [Post 1 content]
2021-09-01 11:55:09.786 TRACE 9884 --- [nio-8089-exec-2] o.h.type.descriptor.sql.BasicBinder      
: binding parameter [2] as [VARCHAR] - [Post 1 description]
2021-09-01 11:55:09.786 TRACE 9884 --- [nio-8089-exec-2] o.h.type.descriptor.sql.BasicBinder      
: binding parameter [3] as [VARCHAR] - [Post1]
2021-09-01 11:55:09.815 ERROR 9884 --- [nio-8089-exec-2] 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: not-null property references a null 
or transient value : com.techspring.entity.Comment.post; nested exception is 
org.hibernate.PropertyValueException: not-null property references a null or transient value : 
com.techspring.entity.Comment.post] with root cause

My MVCs are as follows:

Post.java

@Entity
@Table(name = "posts")
public class Post {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String title;
  private String description;
  private String content;
  @OneToMany(cascade = CascadeType.ALL,
        fetch = FetchType.LAZY,
        mappedBy = "post")
  private Set<Comment> comments = new HashSet<>();

  GET, SET;

Comment.java

@Entity
@Table(name = "comments")
public class Comment {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  private String text;

  @ManyToOne(fetch = FetchType.LAZY, optional = false)
  @JoinColumn(name = "post_id", nullable = false)
  private Post post;

  Get, Set;

PostController.java

RestController public class PostController {

@Autowired
private PostRepository postRepository;

@PostMapping("/posts")
public Post createPost(@Valid @RequestBody Post post) {
    return postRepository.save(post);
}      

CommentController.java

@RestController
public class CommentController {

@Autowired
private CommentRepository commentRepository;

@Autowired
private PostRepository postRepository;


@PostMapping("/posts/{postId}/comments")
public Comment createComment(@PathVariable (value = "postId") Long postId,
                             @Valid @RequestBody Comment comment) {
    return postRepository.findById(postId).map(post -> {
        comment.setPost(post);
        return commentRepository.save(comment);
    }).orElseThrow(() -> new ResourceNotFoundException("PostId " + postId + " not found"));

Appreciate for the help.

2 Answers

The way you do it Comment comment is a new transient entity which is related to an already maintained entity of post. During save first hibernate will try to update post to match with that comment but that comment does not exist yet and so it fails.

A better workflow for you would be the following.

It makes better sense to return the Post with all coments as return and also just append the new comment to an already existing post. That way hibernate will first go and create the comment entity and then relate it to post which already exists and will not face any problem.

@PostMapping("/posts/{postId}/comments")
public Post createComment(@PathVariable (value = "postId") Long postId,
                             @Valid @RequestBody Comment comment) {
    Optional<Post> postOpt = postRepository.findById(postId);
      if (postOpt.isPresent()) {
        comment.setPost(postOpt.get()); <----------------
        postOpt.get().getComments().add(comment);
        postRepository.save(postOpt.get());
        return postOpt.get();
      } else {
        throw new ResourceNotFoundException("PostId " + postId + " not found");
      }

   }

I had a similar issue and to solve it you need to set the parent into child object. It'll be something like this

post.getComments().stream().forEach(c -> c.setPost(post));
Related