1. 게시글 읽기(Read) 및 댓글 기능 구현
- 게시글을 읽는데 가장 중요한 조건 몇가지가 있다.
- 로그인된 회원만 볼 수 있다.
- Board Table에 있는BoardId와 게시판 도메인이 일치하는경우에 의해서만 게시글이 존재한다.
- 게시판이 있을 경우 게시글을 띄울때 그 게시글의 인덱스 번호를 활용해 어디 게시판의 몇번째 게시글인지 알아야 한다.
이를 줄여서 aid(ArticleId)라고 하고 bbs/write?aid=index값으로 도메인에 표시하려고 한다. 다음은 aid값을 구하는 과정을 기능구현한 것이다.
<select id="selectArticleIndex"
resultType="dev.babsang.studymemberbbs.vos.bbs.ArticleReadVo">
SELECT `index` AS `index`,
`user_email` AS `userEmail`,
`board_id` AS `boardId`,
`title` AS `title`,
`content` AS `content`,
`view` AS `view`,
`written_on` AS `writtenOn`,
`modified_on` AS `modifiedOn`,
`user`.`nickname` AS `userNickname`
FROM `study_bbs`.`articles`
LEFT JOIN `study_member`.`users` AS `user` ON `articles`.`user_email` = `user`.`email`
WHERE BINARY `index` = #{index}
ORDER BY `index` DESC
LIMIT 1
</select>
- 게시글의 인덱스번호를 Select하는 쿼리문(xml)이다. LEFT JOIN테이블에 대해서는 아래에서 자세히 다룰것이다. 우선 WHERE절에서 index의 값이 일치하냐에 대한 것으로 쿼리가 시작된다. 일치할 경우 SELECT를 통해 ArticleReadVoEntity의 모든 값을 Search할 수 있게 된다.
IBbsMapper
ArticleReadVo selectArticleIndex(@Param(value = "index") int index);
- 위와 같이 ArticleReadVo 타입의 selectArticleIndex 메서드를 만들어 Param값으로 index를 받는다. 이때 주의 할 점은 WHERE절 #{}안의 값과 일치하는 값을 변수명으로 지정해야 한다. 아니면 Parameter를 찾지 못한다는 오류가 발생한다.
BbsService getArticle()구현
public ArticleReadVo getArticle(int index) {
return this.bbsMapper.selectArticleIndex(index);
}
- ArticleReadVo타입의 getArticle() 메서드는 Mapper.xml, IBbsMapper와 Controller를 이어주는 징검다리 역할을 한다.
BbsController getRead() 요청방식
@RequestMapping(value = "read", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
//bbs/read 도메인을 띄우기 위한 RequestMapping이다.
public ModelAndView getRead(@RequestParam(value = "aid", required = true) int aid) {
// 이메일은 login메서드를 통해 가져오지만 nickname은 가져오지 않기 때문에 null이뜬다.
ModelAndView modelAndView = new ModelAndView("bbs/read");
if (aid < 0 || this.bbsService.getArticle(aid) == null) {
modelAndView.addObject("result", CommonResult.FAILURE.name());
} else {
ArticleReadVo articleId = this.bbsService.getArticle(aid);
BoardEntity board = this.bbsService.getBoard(articleId.getBoardId());
modelAndView.addObject("result", CommonResult.SUCCESS.name());
modelAndView.addObject("article", this.bbsService.getArticle(aid));
//위에서 받아온 값은 그냥 String이다. 보내줘야 하는것 boardEntity타입의 변수이다.
modelAndView.addObject("aid", articleId.getIndex());
modelAndView.addObject("board", this.bbsService.getBoard(board.getId()));
}
return modelAndView;
}
- IBbsMapper, BbsService에서 index의 값을 int타입으로 받았기 때문에 @RequestParam을 통해 가져온 데이터의 값을 int aid값에 대입한다.
- 이때 @RequestParam의 value는 게시판을 통해 게시글 페이지가 클라이언트에게 보여졌을 경우 도메인에 있는 aid를 나타낸다. (JS에서 responseObject['aid']를 사용하기 위한 변수 어노테이션이다.)
- modelAndView에 addObject객체를 통한 키와 값지정을 통해 HTML에서 th:text를 통해 값을 동적으로 받아온다.
- "article"키는 getArticle(어떠한 인덱스)를 통해 그 인덱스에 관한 값들을 SELECT 하는 역할을 한다. 예를 들어 getArticle(31번 인데스)일 경우 31번 인덱스가 xml에서 where절에서 조건의 일치가 통과가 되면 모든 값들을 SELECT해오는 방식이다.
Js기능 구현
Cover.show('게시글을 등록 중입니다\n잠시만 기다려주세요.');
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('title', form['title'].value);
formData.append('content', editor.getData());
formData.append('bid', form['bid'].value);
xhr.open('POST', './write'); // http://localhost:8080/bbs/write
// formData.append('content',editor.getData());
// formData.append('bid', window.location.href);
// xhr.open('POST', window.location.href); http://localhost:8080/bbs/write?bid=free
xhr.onreadystatechange = () => {
Cover.hide();
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status >= 200 && xhr.status < 300) {
const responseObject = JSON.parse(xhr.responseText);
switch (responseObject['result']) {
case 'not_allowed':
Warning.show('게시글을 작성할 수 있는 권한이 없거나 로그아웃 되었습니다. 확인 후 다시 시도해 주세요.');
break;
case 'success':
const aid = responseObject['aid'];
window.location.href = `read?aid=${aid}`;
break;
default:
Warning.show('알 수 없는 이유로 게시글을 작성하지 못하였습니다. 잠시 후 다시 시도해 주세요.');
break;
}
} else {
Warning.show('서버와 통신하지 못하였습니다. 잠시후 다시 시도해 주세요.');
}
}
}
xhr.send(formData);
};
- JS는 작성할때 마다 새롭다. 방금 전 formData.append()에 대해서 확실하게 알게 되었다. formData.append()를 통해 DB에서 index(AutoIncrement), user_email(session.user)등 값을 가져올 구실이 없는 친구들을 append를 통해 같이 전송해주는 것이다. 즉 화면에서 input에 name이 있는 값들에 대해서 처리를 해준다. bid같은 경우는 히든필드를 사용하여 name이 bid임을 가지고 있으나 클라이언트에게 공개할 필요는 없기에 hidden을 통해 숨겨주었을 뿐 값을 같이 넘겨 주어야 한다.
- JSON이 가진 result의 값이 success일 경우 작성하기 버튼을 눌렀을시 read페이지 이나 read?aid=${aid(컨트롤러에서 작성한)}을 통해 클라이언트에게 보여진다. 여기서 window.location.href는 localhost:8080/bbs/를 나타낸다. 즉 localhost:8080/bbs/read?aid=(index번호값)이 된다.
postRead Controller 기능 구현(댓글 작성 버튼 관련)
@RequestMapping(value = "read", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String postRead(@SessionAttribute(value = "user", required = false) UserEntity user,
CommentEntity comment) {
JSONObject responseObject = new JSONObject();
System.out.println("여기");
if (user == null) {
responseObject.put("result", CommonResult.FAILURE.name().toLowerCase());
} else {
comment.setUserEmail(user.getEmail());
Enum<?> result = this.bbsService.writeComment(comment); // 여기서 성공/실패를 나누고 그결과를 밑에 put()함
System.out.println("여기는?");
responseObject.put("result", result.name().toLowerCase());
}
return responseObject.toString();
}
- user는 session을 통해 로그인 인증이 된 회원의 정보를 가져온다.
- comment.setUserEmail()메서드를 통해 로그인된 클라이언트의 이메일로 업데이트 한다.
- 컨트롤러에서 서비스관련 기능을 구현하는 이유는 Service에서 부터 매개변수를 많이 받으면 컨트롤러에서 똑같이 받아야 하기 때문에 굳이 그럴필요없이 효율성을 높이기 위해 컨트롤러에서 구현할 수 있는 부분은 구현해 준것이다.
writeComment Service 기능 구현
public Enum<? extends IResult> writeComment(CommentEntity comment) {
ArticleEntity article = this.bbsMapper.selectArticleIndex(comment.getArticleIndex());
if(article == null) {
return CommonResult.FAILURE;
}
return this.bbsMapper.insertComment(comment) != 0
? CommonResult.SUCCESS
: CommonResult.FAILURE;
}
- writeComment는 댓글 작성관련 서비스 기능 구현을 한것이다.
- ArticleEntity타입의 article은 selectArticleIndex메서드를 통해 comment.getArticleIndex()의 값을 담고있다.
2. 댓글 기능 구현
comment.xml
CommentEntity[] selectCommentsByArticleIndex(@Param(value = "articleIndex")int articleIndex);
- Entity배열로 받는 이유는 하나의 게시글에는 많은 댓글이 생길 수 있기 때문에 배열로 받아야 한다.
comment mapper
<select id="selectCommentsByArticleIndex"
resultType="dev.babsang.studymemberbbs.entities.bbs.CommentEntity">
SELECT `index` AS `index`,
`comment_index` AS `commentIndex`,
`user_email` AS `userEmail`,
`article_index` AS `articleIndex`,
`content` AS `content`,
`written_on` AS `writtenOn`
FROM `study_bbs`.`comments`
WHERE `article_index` = #{articleIndex}
ORDER BY `index`
</select>
- 다른 select쿼리와 다른점은 없다. 항상 말하든 주의 할점은 WHERE절 변수명과 이름 일치 시키기.
comment Service
public CommentEntity[] getComments(int articleIndex) {
return this.bbsMapper.selectCommentsByArticleIndex(articleIndex);
}
- CommentEntity 배열타입을 받아 Mapper와 Controller를 연결해주는 역할을 한다.
comment Controller(GET방식)
@RequestMapping(value = "comment", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public String getComment(@RequestParam(value = "aid")int articleIndex) {
JSONArray responseArray = new JSONArray();
CommentEntity[] comments = this.bbsService.getComments(articleIndex);
for (CommentEntity comment : comments) {
JSONObject commentObject = new JSONObject();
commentObject.put("index", comment.getIndex());
commentObject.put("commentIndex", comment.getCommentIndex());
commentObject.put("userEmail", comment.getUserEmail());
commentObject.put("articleIndex", comment.getArticleIndex());
commentObject.put("content", comment.getContent());
commentObject.put("writtenOn", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(comment.getWrittenOn()));
// new SimpleDateFormat("형식").format([Date 타입 객체]) : [Date 타입 객체]가 가진 일시를 원하는 형식의 문자열로 만들어 반환한다.
responseArray.put(commentObject);
}
return responseArray.toString();
}
- 댓글은 많은 정보를 담아서 한꺼번에 담아 와야 함으로 JSON객체 방식으로 값을 받아온다.
- JSONArray는 Object를 배열로 담을수 있는 JSON 배열 방식이다.
- CommentEntity배열 타입의 comments에 위에서 RequestParam으로 받아온 articleIndex값을 대입한다.
- 이후 향상된 for문을 통해 반복을 시켜주는 값을 JSONObject 객체에 하나씩 담아준다.
- 그후 마지막에 각자 담긴 commentObjce객체 값을 다시 responseArray객체에 담아준다.
- 아래와 같이 배열에 object방식으로 키와 값들이 들어간 배열이 생성되었다. (개발자 도구 Response값임).
Comment JS 기능 구현
if (commentForm != null) {
commentForm.onsubmit = e => {
e.preventDefault();
if (commentForm['content'].value === '') {
alert('댓글을 입력해 주세요.')
commentForm['content'].focus();
return false;
}
Cover.show('댓글을 작성하고 있습니다. \n잠시만 기다려 주세요.');
const xhr = new XMLHttpRequest();
const formData = new FormData();
formData.append('articleIndex', commentForm['aid'].value);
formData.append('content', commentForm['content'].value);
// formData.append는 값이 넘겨져야 할것들만 넘겨준다. index, commentIndex, userEmail, writtenOn은 각자 자신의 값이 따로 넘겨진다. index= autoIncrement, commentIndex는 null값이 들어감, userEmail은 컨트롤러에서 로그인된 session.user의 값으로 들어간다. writtenOn은 default now(xml에서 처리)로 저 두개의 값만 넘겨주면 된다.
xhr.open('POST', './read');
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
Cover.hide();
if (xhr.status >= 200 && xhr.status < 300) {
const responseObject = JSON.parse(xhr.responseText);
switch (responseObject['result']) {
case 'success':
alert('성공임');
break;
default:
alert('알 수 없는 이유로 댓글을 작성하지 못하였습니다.\n\n잠시후 다시 시도해 주세요.');
}
} else {
alert('서버와 통신하지 못하였습니다. \n\n잠시후 다시 시도해 주세요.')
}
}
};
xhr.send(formData);
}
}
- 가장 처음 if조건문을 살펴보면 commentForm이 null이 아닐경우를 기준으로 xhr를 구현한다. null이 아닌경우는 html에서 바로 위와 같이 session.user가 null이 아닐때 즉 로그인된 회원들을 기준으로 form태그를 사용할 수 있게 됨으로 로그인이 안되어있다면 form태그도 클라이언트 브라우저에서는 사라지게 된다.
'SpringBoot' 카테고리의 다른 글
SpringBoot 게시판 기능구현 4 (0) | 2022.11.27 |
---|---|
SpringBoot 게시판 기능구현 2 (0) | 2022.11.21 |
SpringBoot 게시판 기능구현 1 (0) | 2022.11.20 |
SpringBoot 회원가입 기능구현 4 , 비밀번호 재설정 기능구현 (0) | 2022.11.15 |
SpringBoot 로그인 기능 구현, session값을 통한 게시판 접근 (0) | 2022.11.15 |