본문 바로가기

SpringBoot

SpringBoot 게시판 기능구현 2

1. 게시글 작성(write)에 관한 기능 구현

바로전 게시물에서는 게시판의 BoardId를 통해 공지사항, 자유게시판, QnA를 클릭시 그에 맞는 Board의 Text의 값을 가져 오는 것까지 구현하였다. 이번시간에는 게시판에 맞는 게시글 작성에 대한 기능구현을 해보자!

@RequestMapping(value = "write", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView getWrite(@SessionAttribute(value = "user", required = false) UserEntity user,
                             @RequestParam(value = "bid", required = false) String bid) {
    // 현재 이 SessionAttribute는 MemberController의 login 메서드와 연관이 있는 상태이다.
    // 처음 브라우저가 열릴때 도메인 뒤의 값을 읽어와야 하기때문에
    ModelAndView modelAndView;
    if (user == null) {
        modelAndView = new ModelAndView("redirect:/member/login");
    } else {
        modelAndView = new ModelAndView("bbs/write");
        if (bid == null || this.bbsService.getBoard(bid) == null) {
            modelAndView.addObject("result", CommonResult.FAILURE.name());
        } else {
            BoardEntity board = this.bbsService.getBoard(bid);
            modelAndView.addObject("board", this.bbsService.getBoard(bid));
            //위에서 받아온 값은 그냥 String이다. 보내줘야 하는것 boardEntity타입의 변수이다.
            modelAndView.addObject("result", CommonResult.SUCCESS.name());
            modelAndView.addObject("bid", board.getId());
        }
    }
    return modelAndView;
}
  • 게시글 작성시 필요한 정보들이 있다.
    1. UserEntity의 정보는 필요하나 브라우저에서 로그인이 되어있는지 기억하고 있는 user의 값을 가지고 와야 한다. 이를 위해서는 @SessionAttribute 어노테이션을 통해 session에서 기억하고 있는 로그인 인증이 완료 된 클라이언트의 정보를 가져와야 한다. 이때 if조건절에서 user가 null이라함은 로그인 인증이 되지 않은 클라이언트이므로 login창으로 redirect시킨다.
    2. bid == null이라는 뜻은 위 @RequesParam값으로 받아온 순수 bid값이 null일경우를 뜻하고, bbsService에 getBoard(bid)는 실제 DB안 BoardId와 일치하지 않는 값을 뜻한다.
    3. 마지막 else구문에서는 board변수에 실제 DB(BoardID)와 일치하는 Id값이 들어가게 된다.
    4. addObject를 사용하여 Html에서 타임리프를 사용하기 위한 조치를 취해준다.(board는 select를 통한 전체 값을 가져오기 위한 작업이다. bid는 실제 boardEntity의 Id값을 가지고 있다.)

write(Controller)와 관련된 Html

  • 위 자료와 같이 board의 get메서드를 통해 BoardEntity의 값들을 불러올 수 있게 되고 Controller에서 addObject를 한 bid의 값을 input(hidden)으로 둔 이유는 이것을 작성하다가 알게 되었다. 저 값이 없을 경우 JS에서 form.append()를 통한 값을 넣을 곳이 없게 된다. 실제 title,content는 이미 Html 다른 input에서 name이 있지만 bid는 없다. 그러므로 값은 넘겨 주어야 하나 없는경우 히든필드(위에서 type: hidden)을 사용하여 값을 전송한다.
  • bid의 값은 왜 히든필드로 사용했을까? 정답은 굳이 클라이언트에게 보여줄 필요는 없으나 article의 분류를 위해서는 값이 전송되어져야 하기 때문이다. bid를 통해 notice에 index30번 게시글 이렇게 알 수 있기 때문에 반드시 필요한 값이나 보이지는 않아도 상관없기 때문에 히든필드로 작성되어졌다.
  • 와...방금 스톤승현의 말을 곱씹어 보던중 엄청난 사실을 알아냈다... @RequestParam은 대 Stone승현의 말대로 url(도메인)의 주소값의 값을 가져오기 위한 절차였다...그는 도덕책.. RequestParam의 bid로 설정한 값은 body.html의 href값과 일치해야 한다. (예를 들어 th:href=@{bbs/write(bid = 'notice')})라고 하였을 경
public BoardEntity getBoard(String id) {
    return this.bbsMapper.selectBoardById(id);
}
  • getBoard는 Mapper와 Controller를 이어주는 징검다리 역할을 하고있다.
<select id="selectBoardById"
        resultType="dev.babsang.studymemberbbs.entities.bbs.BoardEntity">
    SELECT `id`   AS `id`,
           `text` AS `text`
    FROM `study_bbs`.`boards`
    WHERE BINARY `id` = #{id}
</select>
  • SelectBoardById는 id와 text를 select해줌으로써 실제로 두개의 값을 다 search해주는 역할을 한다. 이때 정말 중요한 사실이 있었다. WHERE BINARY 절에 id = #{id} 이부분에서 #{id}와 아래의 Mapper(interface)의 @Param 값의 이름이 같아야 한다(value = "id" 이부분과 정확히는 같아야 한)... 이것을 모르고있었던 글쓴이는 이것때문에 2시간을 낭비해버렸다.(기초부족)
BoardEntity selectBoardById(@Param(value = "id")String id);

write 관련 JS 기능 구현

over.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':
                        alert('게시글 등록 성공');
                        window.location.href = 'http://localhost:8080/bbs/read?aid=' + responseObject['aid'];
                        break;
                    default:
                        Warning.show('알 수 없는 이유로 게시글을 작성하지 못하였습니다. 잠시 후 다시 시도해 주세요.');
                        break;
                }
            } else {
                Warning.show('서버와 통신하지 못하였습니다. 잠시후 다시 시도해 주세요.');
            }
        }
    }
    xhr.send(formData);
};

write 관련 POST 요청 방식

@RequestMapping(value = "write", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
// RequestMapping의 value값은 전역맵핑 주소/write라는 뜻이다.
@ResponseBody
// Json객체의 값을 받기 위해서는 반드시 사용해야 한다.
public String postWrite(ArticleEntity article,
                        @RequestParam(value = "bid", required = false) String bid,
                        @SessionAttribute(value = "user", required = false) UserEntity user) {
    // ArticleEntity를 매개변수로 받는 이유는 ArticleEntity에 작성되어진 private들의 값에 값을 넣어주기 위해서 사용해야한다. 이때 로그인되어진 user, boardid는 검증이 되어진 값을 가져와야 하기 때문에 또다른 방식을 사용해야한다.
    // 첫번째 RequestParam을 사용하여 (가져올 데이터 이름(키), 요구 필요성에 대한 정의 , 가져온 데이터를 담을 변수) 이렇게 해서 bid를 사용한다. BoardId임으로 id값을 String타입의 bid에 담는다.
    // 두번째 SessionAttribute를 통해 session(로그인이 인증된 user의 값을 가져오기 위해) UserEntity타입의 user를 받아온다.
    // 위에서 둘다 required false를 사용하였기 때문에 null이 아닌 반드시 값이 있어야 한다.
    Enum<?> result;
    if (user == null) {
        // user가 null이라는 뜻은 로그인 기억이 인증된 회원이 아니다 라는 뜻이므로 게시글 작성권한이 없는 결과 값으로 처리한다.
        result = WriteResult.NOT_ALLOWED;
    } else if (bid == null) {
        result = WriteResult.NO_SUCH_BOARD;
        // bid값이 null이라는 뜻은 가져온 데이터의 값이 실제 DB Board Table의 id값과 일치하지 않는 뜻이므로 게시판을 찾을 수없다라는 결과를 도출한다.
    } else {
        article.setUserEmail(user.getEmail());
        // 위의 if조건문을 통과했다는 뜻은 login이 되었고 boardId값도 일치한다는 뜻이므로 이때는 게시판에 게시글을 작성할 권한을 부여해준다.
        // article객체에 setUserEmail을 통해 게시글 작성시 로그인한 사람의 이메일로 업데이트 해준다.
        article.setBoardId(bid);
        // 위와 마찬가지로 article의 setBoardId를 통해 article Table에 값을 넣기 위해 bid의 값으로 업데이트 해준다.
        result = this.bbsService.write(article);
        // result값은 bbsService에서 만든 write메서드에 article객체를 넣은 값 (INSERT INTO쿼리를 실행해주는 메서드)으로 대입한다.
    }
    int index = article.getIndex();
    //number의 값을 article의 getIndex()값으로 대입한다. 이것이 왜 될까? 위에서 ArticleEntity를 매개변수로 받았기 때문에 현재 article객체는 Index값을 당연히 땡겨올수 있게된다.
    // 위와 같이 sout을 하고 작성하기 버튼을 눌렀을시  Index번호가 console창에 찍히는 것을 확인할 수 있다. 하지만 찍히기만 해서는 안된다. 왜냐 결론은 JS까지 거쳐서 그 index값을 주소(도메인)에 찍는 것이 목표이기 때문이다.
    JSONObject responseObject = new JSONObject();
    responseObject.put("result", result.name().toLowerCase());
    if (result == CommonResult.SUCCESS) {
        // 이부분에서 인덱스의 값을 responseObject에 put을 통해 값을 추가하면 JS에서 도메인에 띄울수 있게 된다. xhr을 통해 responseObject['index']를 하게 되면 Controller부터 받아온 값을 띄울 수 있다.
        responseObject.put("aid", index);
    }
    return responseObject.toString();
}
  • 솔직히 POST부분이 조금 많이 헷갈렸다. 지금 구현하던 기능은 이미 올바른 게시판을 들어왔을 경우(클라이언트가) 게시글 작성을 눌렀을때 aid(ArticleId)에 맞게 새로운 페이지가 열리게 하는 것이다. 
  • 게시글 작성 버튼을 눌렀을 경우의 상황이 발생하는 것이므로 작성버튼을 눌렀을때 insert가 되어야한다.(Article Table에)
  • 매개변수는 GET요청방식과 똑같이 받는다(로그인 인증이 된 user, 문자열 타입의 bid)
  • 모든 if조건문을 통과한 결과라면 article의 Email은 userEmail, BoardId는 bid로 업데이트 해준다.
  • 이때 article의 getIndex를 통해 게시글의 index번호를 가져올 수 있게 된다. 그래서 JSON객체화를 통해 값을 JS에 넘겨 줄때 SUCCESS일 경우 responseObject.put 메서드를 통해 index번호를 JS에 전송한다.
  • 위의 aid는 JS에서 responseObject['aid']이곳에 사용하기 위해 작성된 것이다.