본문 바로가기
Back-end/Spring Boot + REST API

Spring boot - blog application (REST API) : Comment

by javapp 자바앱 2022. 7. 3.
728x90

 

Creating JPA "Comment" Entity

 

Comment

@Data
@NoArgsConstructor
@AllArgsConstructor

@Entity
@Table(name = "comments")
public class Comment 
{
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	private String body;
	private String email;
	private String name;
	
	@ManyToOne(fetch =FetchType.LAZY)
	@JoinColumn(name="post_id", nullable=false)
	private Post post;

}

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

 

 

FetchType.LAZY

Hibernate to only Fetch the related entities from the database

when you use the relationship

 

@JoinColumn(name="post_id", nullable=false)

외래키 명 : post_id

 

실행 결과

Hibernate: create table comments (id bigint not null auto_increment, body varchar(255), email varchar(255), name varchar(255), post_id bigint not null, primary key (id)) engine=InnoDB
Hibernate: alter table comments add constraint FKh4c7lvsc298whoyd4w9ta25cr foreign key (post_id) references posts (id)

mysql


Post

@Data // getter setter requiredArgsConstructor toString
@NoArgsConstructor
@AllArgsConstructor

@Entity
@Table(name="posts", uniqueConstraints = {@UniqueConstraint(columnNames= {"title"})})
public class Post 
{
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(name="title", nullable=false)
	private String title;
	
	@Column(name="description", nullable=false)
	private String description;
	
	@Column(name="content", nullable=false)
	private String content;
	
	@OneToMany(mappedBy="post", cascade= CascadeType.ALL, orphanRemoval= true)
	private Set<Comment> comments = new HashSet<>();
	
}

@OneToMany(mappedBy="post", cascade= CascadeType.ALL, orphanRemoval= true)
private Set<Comment> comments = new HashSet<>();

 

 

cascade는 Entity의 상태가 변화했을 때 관계가 있는 Entity에도 상태 변화를 전파시키는

orphanRemoval=true는 관계가 끊어진 child를 자동으로 제거

 

참고 : Cascade 이해 및 orphanRemoval=true vs CascadeType.REMOVE

 

 


 

Creating JPA Repository

public interface CommentRepository extends JpaRepository<Comment, Long>
{

}

 

SimpleJpaRepository@Repository 애노테이션이 선언돼있기 때문에 CommentRepository 위에 애노테이션을 선언할 필요가 없다.

 

@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<t, id=""> implements JpaRepositoryImplementation<t, id=""> { .. }</t,></t,>

 

 


 

Creating Comment DTO

Create CommentDTO class to send the data between client and server.

@Data
public class PostDto 
{
	private long id;
	private String title;
	private String description;
	private String content;
}

 

 


 

CRUD

Create Comment

Service

public interface CommentService 
{
	CommentDto createComment(long postId, CommentDto commentDto);
}

ServiceImpl

@Service
public class CommentServiceImpl implements CommentService
{
	private CommentRepository commentRepository;
	private PostRepository postRepository;
	
	@Autowired
	public CommentServiceImpl(CommentRepository commentRepository, PostRepository postRepository) {
		super();
		this.commentRepository = commentRepository;
		this.postRepository = postRepository;
	}


	@Override
	public CommentDto createComment(long postId, CommentDto commentDto) {
		
		// retrieve post entity by id
		Post post = postRepository.findById(postId).orElseThrow(() -> new ResourceNotFoundException("Post", "id", postId));
		
		// set Post to comment entity
		Comment comment = mapToEntity(commentDto);
		comment.setPost(post);

		// comment entity to DB
		Comment newComment = commentRepository.save(comment);
		
		return mapToDTO(newComment);
	}
	
	private CommentDto mapToDTO(Comment comment)
	{
		CommentDto commentDto = new CommentDto();
		commentDto.setId(comment.getId());
		commentDto.setName(comment.getName());
		commentDto.setEmail(comment.getEmail());
		commentDto.setBody(comment.getBody());
		return commentDto;
	}
	private Comment mapToEntity(CommentDto commentDto)
	{
		Comment comment = new Comment();
		comment.setId(commentDto.getId());
		comment.setName(commentDto.getName());
		comment.setEmail(commentDto.getEmail());
		comment.setBody(commentDto.getBody());
		return comment;
	}
}

mapToDTO, mapToEntity 추가

 

Controller

@RestController
@RequestMapping("/api")
public class CommentController 
{
	private CommentService commentService;

	public CommentController(CommentService commentService) {
		super();
		this.commentService = commentService;
	}
	
	@PostMapping("/posts/{postId}/comments")
	public ResponseEntity<CommentDto> createComment(
                    @PathVariable long postId,
                    @RequestBody CommentDto commentDto){
        	return new ResponseEntity<>(commentService.createComment(postId, commentDto), HttpStatus.CREATED);
	}
}

 

TEST

post man

 

 

MySQL


 

Gat All Comments By Post Id

Service 

	List<CommentDto>  getCommentsByPostId(long postId);

ServiceImpl

	@Override
	public List<CommentDto> getCommentsByPostId(long postId) 
	{
		List<Comment> comments = commentRepository.findByPostId(postId);
		
		return comments.stream().map(comment -> mapToDTO(comment)).collect(Collectors.toList());
	}

 

CommentRepository

public interface CommentRepository extends JpaRepository<Comment, Long>
{

	List<Comment> findByPostId(long postId);

}

 

Controller

	// get all comments by postid
	@GetMapping("/posts/{postId}/comments")
	public List<CommentDto> getCommentsByPostId(@PathVariable Long postId){
		return commentService.getCommentsByPostId(postId);
	}

 

TEST

http://localhost:8080/api/posts/2/comments

Post id 가 2인 모든 Comment

 


Get Comment By Id

Service

	CommentDto getCommentById(Long postId, Long commentId);

ServiceImpl

	@Override
	public CommentDto getCommentById(Long postId, Long commentId) {
		Post post = postRepository.findById(postId).orElseThrow(
				()-> new ResourceNotFoundException("Post", "id", postId));
		
		Comment comment =commentRepository.findById(commentId).orElseThrow(
				()-> new ResourceNotFoundException("Comment", "id", commentId));
		
		if(!post.getId().equals(comment.getPost().getId())) {
			throw new BlogAPIException(HttpStatus.BAD_REQUEST	, "Comment does not belong to post");
		}
		
		return mapToDTO(comment);
	}

 

Exception 추가

비지니스 로직이나 유효성 검사시 예외 처리

//whenever we write some business logic or validate request parameters
public class BlogAPIException extends RuntimeException
{
	private HttpStatus status;
	private String message;
	
	
	public BlogAPIException(String message, HttpStatus status, String message2) {
		super(message);
		this.status = status;
		message = message2;
	}

	public BlogAPIException(HttpStatus status, String message) {
		super();
		this.status = status;
		this.message = message;
	}

	public HttpStatus getStatus() {
		return status;
	}

	@Override
	public String getMessage() {
		return message;
	}
}

 

 

Controller

	@GetMapping("/posts/{postId}/comments/{commentId}")
	public ResponseEntity<CommentDto> getCommentById(@PathVariable Long postId, @PathVariable Long commentId){
		return new ResponseEntity<>(commentService.getCommentById(postId, commentId), HttpStatus.OK);
	}

 

TEST

 

Comment 값은 있는데

게시물에 해당 Comment가 없을 경우

http://localhost:8080/api/posts/1/comments/1

사용자 정의 예외 (BlogAPIException) 발생


Comment 값도 없고

게시물에 해당 Comment가 없을 경우

사용자 정의 예외 (ResourceNotFoundException) 발생

 


Update Comment By Id 

수정하기

CommentService

	CommentDto updateComment(Long postId, long commentId, CommentDto commentRequest);

CommentServiceImpl

	@Override
	@Transactional
	public CommentDto updateComment(Long postId, long commentId, CommentDto commentRequest) {
		
		Post post = postRepository.findById(postId).orElseThrow(
				()-> new ResourceNotFoundException("Post", "id", postId));
		
		Comment comment =commentRepository.findById(commentId).orElseThrow(
				()-> new ResourceNotFoundException("Comment", "id", commentId));
		
		if(!post.getId().equals(comment.getPost().getId())){
			throw new BlogAPIException(HttpStatus.BAD_REQUEST,"Comment does not belongs to post");
		}
		
		comment.setName(commentRequest.getName());
		comment.setEmail(commentRequest.getEmail());
		comment.setBody(commentRequest.getBody());
		
		return mapToDTO(comment);
	}


CommentController

	@PutMapping("/posts/{postId}/comments/{id}")
	public ResponseEntity<CommentDto> updateComment(@PathVariable Long postId, @PathVariable(value="id") Long commentId, 
					@RequestBody CommentDto commentDto)
	{
		return new ResponseEntity<>(commentService.updateComment(postId, commentId, commentDto), HttpStatus.OK);
	}

 

TEST

post man

 

STS 콘솔창에서 update 쿼리가 나가는 것을 확인

Hibernate: select comment0_.id as id1_0_0_, comment0_.body as body2_0_0_, comment0_.email as email3_0_0_, comment0_.name as name4_0_0_, comment0_.post_id as post_id5_0_0_ from comments comment0_ where comment0_.id=?
Hibernate: update comments set body=?, email=?, name=?, post_id=? where id=?

 


Delete Comment

Service

	void deleteComment(Long postId, long CommentId);

ServiceImpl

	@Override
	public void deleteComment(Long postId, long commentId) {
		Post post =  postRepository.findById(postId).orElseThrow(
				()-> new ResourceNotFoundException("Post", "id",postId	));
		
		Comment comment =commentRepository.findById(commentId).orElseThrow(
				()-> new ResourceNotFoundException("Comment", "id", commentId));
		
		if(!post.getId().equals(comment.getPost().getId())) {
			throw new BlogAPIException(HttpStatus.BAD_REQUEST, "Comment does not belongs to post");
		}
		
		commentRepository.delete(comment);
	}

 

Controller

	@DeleteMapping("/posts/{postId}/comments/{id}")
	public ResponseEntity<String> deleteComment(@PathVariable Long postId, @PathVariable(value="id") Long commentId)
	{
		commentService.deleteComment(postId, commentId);
		return new ResponseEntity<String>("Comment deleted Successfully", HttpStatus.OK);
	}

 

TEST

Hibernate: delete from comments where id=?

 

 

 

댓글