JAVA

스트링부트 블로그 만들기 – 9강 댓글

blog app

스트링부트 블로그 만들기 – 9강 댓글

댓글 쓰기

database
데이터베이스 모델 설계하는 규칙하나의 컬럼으로 설명할 수 있는가(작성자, 언제, 어느 게시글에) -> 불가능 보드 테이블에 코멘트 관련 칼럼 추가할 수 있나 -> 가능 한 사람 이상의 코멘트를 한 칼럼으로 처리할 수 있나 -> 불가능 결론 : 코멘트 테이블을 따로 만들어야 함...
1.commant 패키지를 새로 만들어 댓글 모델과 레파지토리를 만들어준다.
package com.cos.blogapp.domain.comment;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import com.cos.blogapp.domain.board.Board;
import com.cos.blogapp.domain.user.User;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Table(name = "comment")
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class Comment {
  
  @Id 
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private int id; 
  
  @Column(nullable = false)
  private String content;
  
  @JoinColumn(name = "userId")
  @ManyToOne
  private User user;
  
  @JoinColumn(name = "boardId")
  @ManyToOne
  private Board board;
  
}
package com.cos.blogapp.domain.comment;

import org.springframework.data.jpa.repository.JpaRepository;

public interface CommentRepository extends JpaRepository<Comment, Integer> {
  
}

 

2.detail.jsp 파일에서 댓글 등록 버튼 클릭했을 때 넣을 액션을 추가한다.
<div class="card">
  <!-- 댓글 쓰기 시작 -->
    <form action="/board/${boardEntity.id}/comment" method="post">
      <div class="card-body">
        <textarea name="content" class="form-control" rows="1"></textarea>
      </div>
      <div class="card-footer">
        <button type="submit" id="btn-reply-save" class="btn btn-primary">등록</button>
      </div>
    </form>
    <!-- 댓글 쓰기 끝 -->
  </div>

 

3.CommentSaveReqDto.java 파일을 만들어 댓글 등록에 필요한 데이터를 담아준다.
package com.cos.blogapp.web.dto;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommentSaveReqDto {
  
  @Size(min = 1, max = 255)
  @NotBlank
  private String content;
  
}

 

4.BoradController.java 파일에서 댓글 등록 메서드를 추가한다.
@PostMapping("/board/{boardId}/comment")
  public String commentSave(@PathVariable int boardId, CommentSaveReqDto dto) {
    
    Comment comment = new Comment();
    
    User principal = (User) session.getAttribute("principal");
    Board boardEntity = boardRepository.findById(boardId)
        .orElseThrow(()-> new MyNotFoundException("해당 게시글을 찾을 수 없습니다."));
    
    comment.setContent(dto.getContent());
    comment.setUser(principal);
    comment.setBoard(boardEntity);

    commentRepository.save(comment);
    
    return "redirect:/board/"+boardId; 
  }
댓글에서 사용할 데이터 중에 User 정보는 세션값을 사용하고 Board 정보는 글번호를 사용합니다.
spring
즉시로딩(Eager)과 지연로딩(Lazy)즉시로딩(Eager)과 지연로딩(Lazy) 예제로 이해하기  데이터베이스의 사용자 테이블(User) id ...
5.Board.java에서 Comment.java와 양방향 매핑을 할 수 있게 만들어준다.
@JsonIgnoreProperties({"board"})
@OneToMany(mappedBy =  "board", fetch = FetchType.LAZY)
private List<Comment> comments;
6.Lazy 전략을 사용하기위해 yml 파일에서 open-in-view 설정을 true로 만들어준다.
jpa:
   open-in-view: true
   hibernate:
     ddl-auto: none
     naming:
       physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
   show-sql: true
7.datil.jsp 파일에서 댓글을 볼 수 있게 반복문으로 출력한다.
<!-- 댓글 시작 -->
 			<c:forEach var="comment" items="${boardEntity.comments}">
 				<li id="reply-${comment.id }"
 					class="list-group-item ds-flex justify-content-between">
 					<!-- LAZY loading 일어남 :  사용직전 -->
 					<div>${comment.content}</div>
 					<div class="d-flex">
 						<div class="font-italic">작성자 : ${commment.user.username} &nbsp;</div>
 						<button class="badge">삭제</button>
 					</div>
 				</li>
 			</c:forEach>
 			<!-- 댓글 끝 -->

 

댓글 서비스 만들기

컨트롤러에서 댓글등록의 서비스 코드를 댓글 서비스 파일로 따로 빼준다.
package com.cos.blogapp.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.cos.blogapp.domain.board.Board;
import com.cos.blogapp.domain.board.BoardRepository;
import com.cos.blogapp.domain.comment.Comment;
import com.cos.blogapp.domain.comment.CommentRepository;
import com.cos.blogapp.domain.user.User;
import com.cos.blogapp.handler.ex.MyNotFoundException;
import com.cos.blogapp.web.dto.CommentSaveReqDto;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class CommentService {

  private final CommentRepository commentRepository;
 	private final BoardRepository boardRepository;

 	@Transactional(rollbackFor = MyNotFoundException.class)
 	public void 댓글등록(int boardId, CommentSaveReqDto dto, User principal) {

 		Board boardEntity = boardRepository.findById(boardId)
 				.orElseThrow(() -> new MyNotFoundException("해당 게시글을 찾을 수 없습니다."));

 		Comment comment = new Comment();
 		comment.setContent(dto.getContent());
 		comment.setUser(principal);
 		comment.setBoard(boardEntity);

 		commentRepository.save(comment);
 	} // 트랜잭션 종료
  
}
private final CommentService commenetservice;

@PostMapping("/board/{boardId}/comment")
  public String commentSave(@PathVariable int boardId, CommentSaveReqDto dto) {
    User principal = (User) session.getAttribute("principal");
    
    commentService.댓글등록(boardId, dto, principal); // -> 디비와 관련된 트랜젝션을 서비스로 이동 
    return "redirect:/board/"+boardId;  
  }
댓글 쓰기 컨트롤러를 BoardController에 넣는 이유는 댓글 쓰기 요청이 게시글 페이지를 거쳐서 등록되기 때문이다.

권한 체크

로그인한 사람만 댓글을 쓸 수 있도록 권한 체크를 한다.
private final CommentService commenetservice;

@PostMapping("/board/{boardId}/comment")
  public String commentSave(@PathVariable int boardId, CommentSaveReqDto dto) {
    User principal = (User) session.getAttribute("principal");
    
    if (principal == null) {
      throw new MyNotFoundException("인증이 되지 않았습니다.");
    }
    
    commentService.댓글등록(boardId, dto, principal); // -> 디비와 관련된 트랜젝션을 서비스로 이동 
    return "redirect:/board/"+boardId;  
  }

 

Comment 객체 내부 필드 제외

Board.java에서 @JsonIgnoreProperties 어노테이션을 추가한다.
package com.cos.blogapp.domain.board;

import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;

import com.cos.blogapp.domain.comment.Comment;
import com.cos.blogapp.domain.user.User;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Table(name = "board")
@AllArgsConstructor
@NoArgsConstructor
@Data 
@Entity
public class Board {
  
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private int id; //PK (자동증가 번호)
  
  @Column(nullable = false, length = 70)
  private String title; // 아이디
  
  @Lob
  private String content;
  
  @JoinColumn(name = "userId") 
  @ManyToOne(fetch = FetchType.EAGER) 
  private User user;
  
  @JsonIgnoreProperties({"board"}) 
  @OneToMany(mappedBy =  "board", fetch = FetchType.LAZY)
  @OrderBy("id desc")
  private List<Comment> comments;

}

 

댓글 삭제

1.서비스 컨트롤러에 댓글삭제 서비스를 만든다.
package com.cos.blogapp.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.cos.blogapp.domain.board.Board;
import com.cos.blogapp.domain.board.BoardRepository;
import com.cos.blogapp.domain.comment.Comment;
import com.cos.blogapp.domain.comment.CommentRepository;
import com.cos.blogapp.domain.user.User;
import com.cos.blogapp.handler.ex.MyAsyncNotFoundException;
import com.cos.blogapp.handler.ex.MyNotFoundException;
import com.cos.blogapp.web.dto.CommentSaveReqDto;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
public class CommentService {

  private final CommentRepository commentRepository;
 	private final BoardRepository boardRepository;

 	@Transactional(rollbackFor = MyAsyncNotFoundException.class)
 	public void 댓글삭제(int id, User principal) {
 		Comment commentEntity =  commentRepository.findById(id)
 			.orElseThrow(()-> new MyAsyncNotFoundException("없는 댓글 번호입니다."));

 		if(principal.getId() != commentEntity.getUser().getId()) {
 			throw new MyAsyncNotFoundException("해당 게시글을 삭제할 수 없는 유저입니다.");
 		}

 		commentRepository.deleteById(id);
 	}

 	@Transactional(rollbackFor = MyNotFoundException.class)
 	public void 댓글등록(int boardId, CommentSaveReqDto dto, User principal) {

 		Board boardEntity = boardRepository.findById(boardId)
 				.orElseThrow(() -> new MyNotFoundException("해당 게시글을 찾을 수 없습니다."));

 		Comment comment = new Comment();
 		comment.setContent(dto.getContent());
 		comment.setUser(principal);
 		comment.setBoard(boardEntity);

 		commentRepository.save(comment);
 	} 
  
}
2.CommentController.java 파일을 만들어 댓글 삭제 컨트롤러를 만든다.
package com.cos.blogapp.web;

import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;

import com.cos.blogapp.domain.user.User;
import com.cos.blogapp.handler.ex.MyAsyncNotFoundException;
import com.cos.blogapp.service.CommentService;
import com.cos.blogapp.web.dto.CMRespDto;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class CommentController {
  
  private final CommentService commentService;
  private final HttpSession session;
  
  @DeleteMapping("/comment/{id}")
 	public @ResponseBody CMRespDto<?> deleteById(@PathVariable int id){
 		User principal = (User) session.getAttribute("principal");
 		if(principal == null) {
 			throw new MyAsyncNotFoundException("인증되지 않은 사용자입니다");
 		}

 		commentService.댓글삭제(id, principal);
 		return new CMRespDto<>(1, "성공", null);
 	}
  
}
3.deital.jsp 파일에서 ajax 요청을 한다.
<div class="card">
    <div class="card-header">
      <b>댓글 리스트</b>
    </div>
    <ul id="reply-box" class="list-group">
      <!-- 댓글 시작 -->
      <c:forEach var="comment" items="${boardEntity.comments}">
        <li id="reply-${comment.id }"
          class="list-group-item ds-flex justify-content-between">
          <!-- LAZY loading 일어남 :  사용직전 -->
          <div>${comment.content}</div>
          <div class="d-flex">
            <div class="font-italic">작성자 : ${comment.user.username} &nbsp;</div>
 						<button class="badge" id="reply"  onClick="deleteById(${comment.id})">삭제</button>
          </div>
        </li>
      </c:forEach>
      <!-- 댓글 끝 -->
    </ul>
    
    <script>
 			async function deleteById(commentId){
 				
 				let response = await fetch("http://localhost:8000/comment/"+commentId, {
 					method:"delete"
 				});
 				
 				let parseResponse = await response.json();
 				
 				if(parseResponse.code == 1){
 					alert("댓글 삭제 성공");
 					//location.reload();
 					$("#reply-"+commentId).remove();
 				}else{
 					alert("댓글 삭제에 실패하였습니다. "+parseResponse.msg);
 				}
 			}
 		</script>
    
  </div>

 

1. ajax 요청 – fetch 사용하기

2. 모든 dom제어는 jquery 사용하기

3. 이벤트 등록 – 전부다 id만들어서 이벤트 리스너 사용

4. for문안에 dom제어만 dom자체에 onClick() 걸기

5. 전체 리로드와 부분 리로드 사용 구분
(1) 전체 리로드 (기본)
(2) 부분 리로드 – 사용자가 적은게 많을때

최신글