스프링/스프링부트+JPA - 인스타

스프링부트+JPA - 인스타그램 클론 코딩 12 - 댓글(모델링/삽입)

H-V 2021. 10. 19. 13:34

이지업 최주호 강사님 강의 참조

 

 

 

01 댓글 모델링

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class Comment {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;
	
	@Column(length = 100, nullable = false)
	private String contentString;
	
	@JoinColumn(name = "userId")
	@ManyToOne(fetch = FetchType.EAGER)
	private User user;
	
	@JoinColumn(name = "imageId")
	@ManyToOne(fetch = FetchType.EAGER)
	private Image image;
	
	private LocalDateTime createDate;
	
	@PrePersist
	public void createDate() {
		this.createDate = LocalDateTime.now();
	}
}
  1. 다른 모델들과 동일하다 필요한것을 선언하고 넣어주자. (길이와 널값처리는 개발자의 의도에 맞게 넣자)
  2. 댓글을 쓸때에는 그 댓글을 쓰는 'User' 정보와 그 댓글이 달리는 'Image'의 정보가 필요하다. 즉 모든 모델링에서 그 모델링이 필요한 다른 모델링이 어떤게 있을까 라고 생각하고 구현하는게 좋다
  3. 유저와 이미지 클래스가 필요하니 같이 선언해주고 다른 테이블이 들고있는 외래키를 @JoinColumn으로 묶어 준다
  4. 연관관계 매핑시에 항상 주 클래스와 땡겨서 넣는 클래스의 관계를 잘 생각하고 주 클래스가 앞에오도록 연관관계를 설정한다
  5. 'fetch' 전략에서는 EAGER/LAZY 전략으로 나뉘는데 한 클래스를 호출시에 딸려오는 다른 클래스가 단순히 하나라면 EAGER, 여러개라면 LAZY로 처리하는게 좋다
    (예: 댓글같은경우는 한 이미지에 한 댓글이 적히는 형태이다(여러개를 쓸수는있지만 한번에 여러개를 동시에 못한다) 즉 한 호출에 하나만 딸려오면 EAGER. 
    (예: 프로필을 불러올때 프로필로 올린 여러 이미지는 한번에 여러개가 불러온다. 한 호출에 여러개가 불러지면 이때는 LAZY.)

01 - 1 레퍼지토리

  • 레퍼지토리를 만드는것은 정말 일도 아니다. 제일 쉽다!
public interface CommentRepository extends JpaRepository<Comment, Integer>{}

 

 

 

 

02 AJAX 통신 방법 세팅

  • 예전 스토리로드 전체를 구현했을때 AJAX로 'storyLoad()'라는 함수를 호출했고 이 함수가 'getStoryItem()'를 불러왔다. 이 getStoryItem() 함수가 'story.jsp'의 스토리 목록을 뿌리는 부분을 들고 있다. 
  1. 숫자 1로 표현되어있던부분을 이제 image.id 로 바꿔주자
    아이디 + 각각의 유저 사진들을 바꿔주면 F12에서 나오는 오류가 사라진다
  2. 댓글 작성시 onClick이 발동되면서 모델링에 세팅되어있든 값들이 필요한데 문제는 userId/imageId를 어디서 받아오냐 이다. 
    이미지 아이디값을 클릭을 하면 AJAX로 받도록 하고 userId 값은 세션으로 처리하면 된다!
  3. addComment()함수에 imageId를 파라미터로 받고 이 함수가 작동되면 뿌릴 수 있도록 댓글 화면에도 아이디값들을 변경할 필요가 있다
    storyCommentItem값은 나중에 댓글 삽입 후 꺼내오기만 하면 된다. 
    변경이 잘 되었다
  4. 이제 댓글을 삽입 시키고 새로운 댓글들이 append 되도록 만들면 된다
    첫번째 AJAX 세팅

 

03 컨트롤러+서비스 구현

  • AJAX 1차 세팅을 기반으로 컨트롤러 + 서비스를 구현해 보자
  • 현재 AJAX에서 'data'로 넘어오는 값을 받아서 DB에 넣는 작업을 구현할텐데 현재 넘어오는 값은 imageId와 content인데 모델링에서 Comment 클래스 객체로 받을 수 없다. 즉 DTO로 만들어서 원하는 데이터만 받을 수 있도록 해야 한다
  1. DTO 세팅
    @Data
    public class CommentDto {
    	private String content;
    	private int imageId;
    }​
  2. CommentApiController
    @RequiredArgsConstructor
    @RestController
    public class CommentApiController {
    	
    	private final CommentService commentService;
    	
    	@PostMapping("/api/comment")
    	public ResponseEntity<?> inserComment(@RequestBody CommentDto commentDto,
    			@AuthenticationPrincipal PrincipalDetails principalDetails){
    		//System.out.println(commentDto);
    		Comment comment = commentService.insertCommnet(commentDto.getContent(), commentDto.getImageId(), principalDetails.getUser().getId());
    		return new ResponseEntity<>(new CMRespDto<>(1, "Success", comment), HttpStatus.CREATED);
    	}
    DTO를 이용해 원하는 값을 받고, 거기다 userId값을 또 받아야하니 세션이 이미 들어가있는 @AuthenticationPrincipal을 통해서 유저값을 끌어오면 된다. 그리고 리턴으로 응답값/메세지/데이터를 돌려주자
  3. CommentService
    @RequiredArgsConstructor
    @Service
    public class CommentService {
    	private final CommentRepository commentRepository;
    	
    	@Transactional
    	public Comment insertCommnet(String content, int imageId, int userId) {
    		
    		//Tip: 객체를 만들때 가짜 객체를 만들어서 심플하게 id값만 insert 가능
    		Image image = new Image();
    		image.setId(imageId);
    		User user = new User();
    		user.setId(userId);
    		
    		Comment comment = new Comment();
    		comment.setContent(content);
    		comment.setImage(image);
    		comment.setUser(user);
    		
    		return commentRepository.save(comment);
    	}​
     서비스가 호출되면 DB에 접근 가능하도록 값들을 세팅해준다. 여기서 ID값을 받아올때 일일이 객체를 셀렉트해서 일일이 받아와야하지만 그렇게하지말고 '가짜 객체'라는것을 만들어서 ID값만 심플하게 받아올수있다.
    하지만 단점으로는 RETURN값으로는 가짜객체에서 원하는 값만 들고온다는 점이다. 
    여기서 CMRespDto의 장점이 나온다.  필요한 데이터가 다 들어와 있다
  4. 여기서 문제는 user가 댓글을 달기때문에 유저의 ID와 USERNAME 둘다 필요 하다는 것이다. 즉 가짜객체로 처리가 안된다는 말이다. 다시 코드를 짜보자.
    	@Transactional
    	public Comment insertCommnet(String content, int imageId, int userId) {
    		
    		//Tip: 객체를 만들때 가짜 객체를 만들어서 심플하게 id값만 insert 가능
    		Image image = new Image();
    		image.setId(imageId);
    		
    		User userEntity = userRepository.findById(userId).orElseThrow(()->{
    			throw new CustomApiException("유저 아이디를 찾을 수 없습니다.");
    		});
    		
    		Comment comment = new Comment();
    		comment.setContent(content);
    		comment.setImage(image);
    		comment.setUser(userEntity);
    		
    		return commentRepository.save(comment);
    	}​

    이제 유저의 모든 값을 다 들고 온다. 즉 필요한 유저값을 찾아서 뿌릴 수 있다는 말!
    또한 처음에는 네이티브쿼리로 만들어 객체를 받아서 (User mSave ...) 진행하려했으나 객체타입으로는 스프링부트에서 받지 않는다. 그렇기 때문에 부트가 지원하는 save로 받아 처리한다. 

  5. 데이터는 잘 들고 왔지만 유저의 'images'는 필요없는 항목이니 Comment.java에 @JsonIgnoreProperties를 걸어주자

 

 

 

 

 

04 댓글 뷰 렌더링

  • story.js 에서 addComment() 함수 마무리
    function addComment(imageId) {
    
    	let commentInput = $(`#storyCommentInput-${imageId}`); //내가 쓰는 댓글
    	let commentList = $(`#storyCommentList-${imageId}`);// 쓰는 댓글이 들어가는 리스트
    
    	let data = {
    		imageId:imageId,
    		content: commentInput.val()
    	}
    
    	if (data.content === "") {
    		alert("댓글을 작성해주세요!");
    		return;
    	}
    	
    	$.ajax({
    		type:"post",
    		url:"/api/comment",
    		data:JSON.stringify(data),
    		contentType:"application/json; charset=utf-8",
    		dataType:"json"
    	}).done(res=>{
    		console.log("성공",res);
    		
    		let comment = res.data;
    		
    		let content = `
    		  <div class="sl__item__contents__comment" id="storyCommentItem-${comment.id}"> 
    		    <p>
    		      <b>${comment.user.username} :</b>
    		      ${comment.content}
    		    </p>
    		    <button><i class="fas fa-times"></i></button>
    		  </div>
    		`;
    
    		commentList.prepend(content); //prepend는 앞에 append는 뒤에 나열
    		
    	}).fail(error=>{
    		console.log("오류",error);
    	});
    	commentInput.val("");
    }​

 

  • 작동이 잘 된다. 하지만 F5로 눌리면 댓글이 다시 사라진다. 이제 프로필 페이지 로드시에 이 함수를 불러서 같이 들고 오도록 세팅을 해야한다. 즉 ImageApiController에서 imageStroy()함수를 불러올때 댓글도 같이 불러오도록 세팅을 해줘야한다. Image.java에 댓글용 변수를 추가하도록 하자. 여기에 무한참조까지 방지를 해주자. 또한 네이티브 쿼리가 아닌 부트에서 지원하는 쿼리의 정렬은 @OrderBy로 처리한다. 
    public class Image {
    	...
    	@OrderBy("id DESC")
    	@JsonIgnoreProperties({"image"})
        	@OneToMany(mappedBy = "image")
    	private List<Comment> comments;
    	...
    }​
  • 이제 story.js로 돌아가 댓글을 뿌리면 된다.