Today's 게시판 댓글 삭제 기능 구현 let's get it!
1. 게시판 댓글 삭제 기능 구현
Delete관련 Controller
@RequestMapping(value = "comment", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String deleteComment(CommentEntity comment,
@SessionAttribute(value = "user", required = false) UserEntity user) {
Enum<?> result = this.bbsService.removeComment(comment, user);
JSONObject responseObject = new JSONObject();
responseObject.put("result", result.name().toLowerCase());
return responseObject.toString();
}
- 게시판 삭제도 JSON객체를 통해 정보를 삭제해야한다. 매일 매일 수업을 들으면서 새롭게 알게되는 사실이 계속 생긴다. 물론 내가 공부를 안했지만. method 요청방식은 실제 기능을 하는 것이 아닌 열거형과 같으므로 요청방식을 나타내는 이름이라고 생각하면 된다. 기능에 대한 구현은 개발자가 직접 작성해주는 것이다. 단지 JS에서 xhr의 요청방식과 이름을 맞추기 위해서 정도만 사용한다고 생각하면 될것 같다.
- 댓글을 삭제하는 사용자는 누구일까? 전부가 다 삭제할 수 있다면 말도 안된다. 단순하게 생각하면 로그인을 하였고 그 로그인 한 사람이 댓글을 쓴사람과 일치하는지를 확인해야 한다.
- 로그인을 한 사람에 대한 정보는 SessionAttribute를 통한 로그인이 인증된 user의 값을 가져온다.
- 로그인 한 사람과 댓글을 쓴사람이 일치하는가에 대해서는 user_email을 통해 확인하면 될것이다.
Delete관련 Xml
<delete id="deleteComment"
parameterType="dev.babsang.studymemberbbs.entities.bbs.CommentEntity">
DELETE
FROM `study_bbs`.`comments`
WHERE `index` = #{index}
</delete>
- delete하는 조건은 index번호(index를 통해서 댓글과 대댓글 모두를 삭제할 수 있기 때문이다.)에 맞는 데이터를 삭제시켜주기 위한 쿼리문이다.
Delete관련 IBbsMapper
int deleteComment(@Param(value = "index") int index);
- int타입인 이유는 index를 변수로 받기 때문이다.
- Param의 value 값은 delete쿼리의 where절의 #{index}의 값이다.
Delete관련 BbsService
public Enum<? extends IResult> removeComment(CommentEntity comment, UserEntity user) {
CommentEntity existingComment = this.bbsMapper.selectCommentByIndex(comment.getIndex());
if (existingComment == null) {
return CommentDeleteResult.NO_SUCH_COMMENT;
} else if (!existingComment.getUserEmail().equals(user.getEmail())) {
return CommentDeleteResult.NOT_ALLOWED;
}
return this.bbsMapper.deleteComment(comment.getIndex()) > 0
? CommonResult.SUCCESS
: CommonResult.FAILURE;
}
- existingComment라는 CommentEntity타입의 변수를 만들어 select쿼리를 사용하기 위한 comment.getIndex()의 값을 대입한다.(BbsMapper의 쿼리의 where절에 있는 index번호와 같으면 service가 실행된다.)
- existingComment가 null일 경우에는 댓글이 없다는 열거형의 결과값을 반환한다.
- existingComment가 지금 현재 CommentEntity의 모든 결과값을 다 담고 있기 때문에 이 친구의 getUserEmail과 user.getEmail(user가 로그인하여 들어온 이메일 즉, 삭제를 클릭하는 클라이언트)의 이메일이 둘다 같지 않다면(!를 통해 전체 문장의 부정의 값을 반환한다.) NOT_ALLOWED 결과값을 반환한다.
- 그것도 아니라면 deleteMethod가 실행되는데 이때 0보다 크다는 뜻은 삭제할 index의 값이 하나 이상있다는 뜻이기 때문에 SUCCESS가 반환되거나 어떠한 이유로 인한 FAILURE가 반환된다.
Delete 관련 JS기능 구현
dom.querySelector('[rel="actionDelete"]')?.addEventListener('click', e => {
e.preventDefault();
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('index', commentObject['index']);
xhr.open('DELETE', './comment');
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
const responseObject = JSON.parse(xhr.responseText);
switch (responseObject['result']) {
case 'success':
loadComments();
break;
case 'no_such_comment':
alert("선택하신 댓글을 찾을 수 없습니다.");
loadComments();
break;
case 'not_allowed':
alert("당신의 이메일이 아닙니다!!! 못지워!!");
loadComments();
break;
default:
alert('알 수없는 이유로 댓글을 삭제하지 못하였습니다. \n\n 잠시 후 다시 시도해 주세요.');
}
} else {
alert('알 수 없는 오류입니당~')
}
}
}
xhr.send(formData);
});
이친구...좀 골치아프다... dom의 관한 설명은 update를 하며 설명을 다시하지만 어쨋든 기존에 JS xhr기능구현을 하는 것과 매우 똑같다.
- 위의 xhr.open('DELETE')이부분이 앞의 Controller의 요청방식과 같아야 작동을 한다. 같은 이름의 같은 요청방식은 하나 밖에 존재하지 못한다.
- SUCCESS일 경우 loadComment()를 통해 삭제되고 난 뒤의 댓글 form을 새로 보여준다.
- 아래의 사진은 왜 index를 fom.append로 던져주어야 하는가에 대한 여러가지 자료이다.(참고)
- 댓글 혹은 대댓글을 삭제버튼을 눌렀을경우 DELETE방식으로 요청이 나간다. 지금의 상황은 대댓글을 지웠다는 상황을 가정하고 의 예시를 들어 볼 것이다.
- 최초 삭제요청을 하였을때 index 번호를 통한 요청을 통해 댓글, 대댓글의 정보를 가져올 수 있다.
- 삭제 요청에 대한 응답으로 GET요청방식이 사용되어진다.
- aid의 값을 payload로 받으면서 게시글의 index와 맞는 게시글을 보여준다. 댓글과 대댓글은 이 aid밑에 계층구조로있기 때문에 aid와 연결된 index의 댓글을 보여주는 방식이다.
- GET을 하게 되면 일단 aid=209번에 있는 모든 댓글과 대댓글의 값을 가져온다. 그이유때문에 GET 응답방식 사용시 aid를 Controller에서 @RequestParam으로 받는 것이다.
- 그중 가장 끝에 있는 index번호를 확인하여 index번호와 같은 것을 삭제 하고 난뒤 보여주는 방식이다. 위에서는 index가 74번인것을 삭제하였으므로 74번이 없어진 값을 GET하여 보여준다.
그럼 이때 삭제버튼이 사용자에게 보여지는 경우는 언제일까?
- 위와 같이 isMine이 true라는 것은 자신의 이메일과 작성한 댓글의 이메일이 같다는 뜻이다.
- isSigned가 true라는 뜻은 로그인을 하였다는 뜻이다.
- 아까 가장 상단에서 말한 로그인을 하고 이메일의 동일성 여부를 체크한 값이 true인 index만 삭제할 수 있는 조치를 JsonArray를 통해 처리하였다.
2. 게시판 댓글 수정 기능 구현
update관련 Controller
@RequestMapping(value = "comment", method = RequestMethod.PATCH, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String updateComment(CommentEntity comment,
@SessionAttribute(value = "user", required = false) UserEntity user) {
Enum<?> result = this.bbsService.updateComment(comment, user);
JSONObject responseObject = new JSONObject();
responseObject.put("result", result.name().toLowerCase());
System.out.println(result); //여기서 실패가 뜬다는건 Service에서 실패임
return responseObject.toString();
}
- PATCH요청방식을 사용한 comment 수정관련 Controller이다.
- DELETE방식과 별다른 차이는 없다. 요청하는 index와 관련된 댓글이나 대댓글을 수정하는 방식이기 때문이다.
update관련 Xml
<update id="updateComment"
parameterType="dev.babsang.studymemberbbs.entities.bbs.CommentEntity">
UPDATE `study_bbs`.`comments`
SET `comment_index` = #{commentIndex},
`user_email` = #{userEmail},
`article_index` = #{articleIndex},
`content` = #{content},
`written_on` = #{writtenOn}
WHERE `index` = #{index}
LIMIT 1
</update>
- update관련 xml이다. index를 기준으로 조건에 부합하면 모든값의 update를 진행할 수 있는 쿼리이다. 모든 값을 가져온 이유는 공용성(편리함)을 위해서이다. 예를 들어content만 가져오게 된다면 다음에 email을 변경하고 싶을경우 또다른 xml쿼리를 작성해야 하기 때문에 댓글(대댓글)관련 update쿼리문은 이것 하나로 끝낼 수 있다.(야호~)
update 관련 IBbsMapper
int updateComment(CommentEntity comment);
- 수정문 기능을 구현하던 중 이부분에서 의외로 크게 당했다(?). 이부분에서 매개변수로 받는 부분을 Param어노테이션을 통해 index를 받았는데 크나큰 나의 착각이였다. index만 받을 경우 Service단에서도 index와 같은 타입의 매개변수를 넣어야 하는데 이렇게 된다면 Update쿼리의 SET부분과의 일치가 하지 않게 되기 때문에 오류가 발생한다. 그러니까 쉽게 예를 들면 컴뷰터 부품점에 무상 수리를 원해서 가게 되었는데 이때 컴퓨터 자체를 들고가지 않고 그안에 부품아무거나 하나만 들고가는 꼴이다. 즉 수정을 content만 하더라도 일단 전체 comment관련 Entitiy의 값을 들고 가서 where절에 index가 일치할 경우 모든 부품을 확인하고 content부분을 수정할 수 있게 해준다고 생각하면 된다.
- 진짜 쉽게 말하면 xml에서 내가 모든 값을 set에 넣었기 때문에 모든 값을 들고와야한다는 뜻이다. = ㅅ =
update 관련 Service
public Enum<? extends IResult> updateComment(CommentEntity comment, UserEntity user) {
CommentEntity existingComment = this.bbsMapper.selectCommentByIndex(comment.getIndex());
System.out.println(existingComment);
if(existingComment == null) {
return CommonResult.FAILURE;
} else if(!existingComment.getUserEmail().equals(user.getEmail())) {
return CommonResult.FAILURE;
}
existingComment.setContent(comment.getContent());
return this.bbsMapper.updateComment(existingComment) > 0
? CommonResult.SUCCESS
: CommonResult.FAILURE;
}
- CommentEntity existingComment변수는 selectCommentByIndex메서드를 통해 comment.getIndex의 값을 받는다.(이구문이 사실상 CommentEntity의 모든값을 담고있기 때문에 select쿼리를 사용할 수 있게 해준다.)
- Delete와 마찬가지로 로그인 유무와 이메일 동일성을 확인 해주고 모든 조건절에 참일 경우 existingcomment.setContent를 통해 현재 입력한 comment의 getContent로 업데이트 해준다. 이때 updateComment의 타입은 int이기 때문에 0보다 크다는 뜻은 update구문이 1개있다는 뜻으로 SUCCESS를 반환하게 해준다.
modifyFormElement.onsubmit = e => {
e.preventDefault();
if (modifyFormElement['content'].value === '') {
modifyFormElement['content'].focus();
return false;
}
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('content', modifyFormElement['content'].value);
formData.append('index', commentObject['index']);
xhr.open('PATCH', './comment');
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
const responseObject = JSON.parse(xhr.responseText);
switch (responseObject['result']) {
case 'success':
loadComments();
break;
default:
alert('수정 실패 ㅅㄱ');
break;
}
}
}
}
xhr.send(formData);
};
commentContainer.append(commentElement, replyFormElement);
};
- formdata.append를 통해 content내용과 index를 전송해준다.
- content에는 현재 user가 입력한 내용을 담아 보내주고 index는 현재 선택한 댓글의 index를 보내준다. 자세한건 아래의 자료를 참조할 것.
- 현재 게시글의 가장 아래의 댓글을 보면 밥주세요 밥! 이다. 근데 이것을 수정하기를 통해 payload의 값이 어떻게 전송되는지 확인해보자!
- PATCH요청방식으로 최초 요청을 보낸다.
- 위에서 form.append를 통해 content를 보내는데 현재 작성한 content내용을 담아서 보낸다. 물론 index도 같이
- GET응답 방식은 모든 요청방식에 대한 대답이므로 항상 존재한다. DELETE 요청때와 똑같이 작동한다.
'SpringBoot' 카테고리의 다른 글
SpringBoot 게시판 기능구현 3 (0) | 2022.11.21 |
---|---|
SpringBoot 게시판 기능구현 2 (0) | 2022.11.21 |
SpringBoot 게시판 기능구현 1 (0) | 2022.11.20 |
SpringBoot 회원가입 기능구현 4 , 비밀번호 재설정 기능구현 (0) | 2022.11.15 |
SpringBoot 로그인 기능 구현, session값을 통한 게시판 접근 (0) | 2022.11.15 |