스프링부트 게시판 상세보기 - seupeulingbuteu gesipan sangsebogi


본 포스팅의 화면 처리는 HTML5 기반의 자바 템플릿 엔진인 타임리프(Thymeleaf)를 사용합니다.

본 포스팅은 단계별(step by step)로 진행되니, 이전 단계를 진행하시는 것을 권장드립니다.


이전 글에서는 게시글 리스트 페이지를 구현해 보았습니다.

이번에는 특정 게시글의 상세정보를 보여주는 게시글 상세(Read) 페이지를 구현해 보도록 하겠습니다.

우리는 게시글 등록 구현하기에서 이미 모든 서비스 로직을 처리하였기 때문에

바로 뷰 영역(Presentation Layer)부터 작업이 진행되며,

리스트와 마찬가지로 컨트롤러와 화면(HTML)만 손봐주면 됩니다.

1. 컨트롤러(Controller)에 메서드 추가하기

PostController에 openPostView( ) 메서드를 작성해 주세요.

    // 게시글 상세 페이지
    @GetMapping("/post/view.do")
    public String openPostView(@RequestParam final Long id, Model model) {
        PostResponse post = postService.findPostById(id);
        model.addAttribute("post", post);
        return "post/view";
    }

전체 로직

게시글 번호(id)를 파라미터로 전달받아 게시글 상세정보를 조회하고,

model의 addAtttribute( )를 이용하여 게시글 응답 객체인 post를 뷰(HTML)로 전달합니다.

2. 화면(View) 생성하기

다음으로 openPostView( )의 리턴 문에 지정된 view.html을 추가해 줄 차례입니다.

src/main/resources의 templates/post 폴더에 view.html을 추가하고, 소스 코드를 작성해 주세요

타임리프의 th 속성, 네임스페이스 선언, 레이아웃 파일 경로 지정에 대한 내용은

분량이 적지 않기 때문에 여기서는 생략하도록 하겠습니다.

혹시라도 궁금하신 분들께서는 여기를 꼭 확인해 주세요!

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="layout/basic">
<th:block layout:fragment="title">
    <title>상세 페이지</title>
</th:block>

<th:block layout:fragment="content">
    <div class="page_tits">
        <h3>게시판 관리</h3>
        <p class="path"><strong>현재 위치 :</strong> <span>게시판 관리</span> <span>리스트형</span> <span>상세정보</span></p>
    </div>

    <div class="content">
        <section>
            <table class="tb tb_row">
                <colgroup>
                    <col style="width:10%;" /><col style="width:23%;" /><col style="width:10%;" /><col style="width:23%;" />
                </colgroup>
                <tbody>
                    <tr>
                        <th scope="row">글 유형</th>
                        <td th:text="${post.noticeYn == false ? '일반' : '공지'}"></td>

                        <th scope="row">등록일</th>
                        <td th:text="${#temporals.format( post.createdDate, 'yyyy-MM-dd HH:mm' )}"></td>
                    </tr>
                    <tr>
                        <th scope="row">제목</th>
                        <td>[[ ${post.title} ]]</td>

                        <th scope="row">조회</th>
                        <td colspan="3">[[ ${post.viewCnt} ]]</td>
                    </tr>
                    <tr>
                        <th scope="row">이름</th>
                        <td colspan="3">[[ ${post.writer} ]]</td>
                    </tr>
                    <tr>
                        <th scope="row">내용</th>
                        <td colspan="3">[[ ${post.content} ]]</td>
                    </tr>
                </tbody>
            </table>
            <p class="btn_set">
                <a th:href="@{/post/write.do( id=${post.id} )}" class="btns btn_bdr4 btn_mid">수정</a>
                <button type="button" class="btns btn_bdr1 btn_mid">삭제</button>
                <a th:href="@{/post/list.do}" class="btns btn_bdr3 btn_mid">뒤로</a>
            </p>
        </section>
    </div> <!--/* .content */-->
</th:block>
</html>

layout:fragment="content"

게시글 작성, 리스트 페이지와 마찬가지로 게시글 상세 페이지의 실제 컨텐츠가 들어갑니다.

openPostView( )에서 뷰(HTML)로 전달한 post 객체를 이용하여

필요한 게시글 정보를 텍스트 형식으로 출력합니다.

전체 버튼

버튼 요청 URI 파라미터 설명
수정하기 "/post/write.do" 게시글 번호(id) 게시글 작성 페이지로 이동합니다.

신규 글 작성과 달리, id를 파라미터로 전달하여

현재 게시글 정보를 화면에 출력합니다

삭제하기 "/post/delete.do" 게시글 번호(id) 해당 버튼에 onclick 이벤트를 바인딩하여

게시글 삭제 URI를 호출합니다.

자세한 내용은 게시글 삭제 구현에서

말씀드리도록 하겠습니다.

뒤로가기 "/post/list.do" X 게시글 리스트 페이지로 이동합니다.

3. 애플리케이션 실행하기

애플리케이션을 실행하고, 브라우저에서 게시글 리스트 페이지로 이동해 주세요.

(이미지 깨짐 문제를 방지하기 위하여 헤더, 푸터, 좌측 메뉴는 제외했습니다!)

스프링부트 게시판 상세보기 - seupeulingbuteu gesipan sangsebogi
게시글 리스트 페이지

리스트 페이지에서 게시글 제목을 클릭해보면, 게시글 상세 페이지로 이동되는 것을 확인하실 수 있습니다.

저는 제일 상단에 있는 공지글 제목을 클릭해 보았습니다.

스프링부트 게시판 상세보기 - seupeulingbuteu gesipan sangsebogi
게시글 상세 페이지

쿼리 스트링 (Query String)

브라우저 상단의 주소창(URL)을 보시면, "/post/view.do?id=1"

이와 같이 ' ? ' 뒤에 게시글 번호(id)가 파라미터로 포함되어 있는 걸 확인하실 수 있습니다.

이렇게 파라미터의 정보가 "Key=Value" 형태(구조)의 문자열로 이루어진 것을 쿼리 스트링이라고 합니다.

쿼리 스트링의 첫 번째 파라미터는 ' ? '로 시작하고, 두 번째 파라미터부터는 ' & '로 구분합니다.

쿼리 스트링으로 연결된 URI는 HTTP 요청 메서드 중 GET 방식임을 의미합니다.

4. 게시글 작성 페이지 수정하기

스프링부트 게시판 상세보기 - seupeulingbuteu gesipan sangsebogi
openPostWrite( ) 구조

앞의 이미지는 PostController의 openPostWrite( ) 메서드의 구조입니다.

게시글 상세 페이지의 수정하기 버튼을 클릭하면 게시글 작성 페이지로 이동하는데요.

이동하는 시점에 파라미터로 게시글 번호(id)를 함께 전달하기 때문에 21번 라인의 조건은 true가 됩니다.

즉, 게시글 상세정보(post)를 뷰(HTML)로 전달한다는 것이죠.

하지만, write.html에는 게시글 상세정보를 화면에 렌더링 해주는 처리가 되어있지 않습니다.

우리는 자바스크립트를 이용하여 사용자에게 게시글 상세정보를 보여주도록 하겠습니다.

우선, JS 영역에 renderPostInfo( ) 함수를 작성하고, onload( ) 함수를 다음과 같이 변경해 주세요.

    window.onload = () => {
      renderPostInfo();
    }


    // 등록일 초기화
    function initCreatedDate() {
      document.getElementById('createdDate').value = dayjs().format('YYYY-MM-DD');
    }


    // 게시글 상세정보 렌더링
    function renderPostInfo() {
      const post = [[ ${post} ]];

      if ( !post ) {
        initCreatedDate();
        return false;
      }

      const form = document.getElementById('saveForm');
      const fields = ['id', 'title', 'content', 'writer', 'noticeYn'];
      form.isNotice.checked = post.noticeYn;
      form.createdDate.value = dayjs(post.createdDate).format('YYYY-MM-DD HH:mm');

      fields.forEach(field => {
        form[field].value = post[field];
      })
    }

window.onload( )

자바스크립트의 onload 이벤트로, 페이지가 실행되는 시점에 한 번만 실행되는 코드입니다.

즉, "/post/write.do" URI가 호출되었을 때, 딱 한 번만 실행되는 셈이죠.

우리는 onload 함수에서 renderPostInfo( )를 호출하여 게시글 상세정보를 렌더링 합니다.

페이지가 로드되는 시점에  한 번만 호출하면 그 이후에는 게시글 정보를 재 렌더링 할 일이 없겠죠?

renderPostInfo( )

게시글 상세정보를 화면에 렌더링 하는 역할을 하는 함수입니다.

신규 게시글 작성인 경우, post를 뷰로 전달하지 않기 때문에 오늘 날짜를 렌더링 한 후 로직을 종료하고,

기존 게시글 수정인 경우에만 메인 로직이 실행됩니다.

딱히 복잡한 로직은 없으니, 디테일한 설명은 생략하도록 하겠습니다!

이제, 게시글 상세 페이지에서 수정하기 버튼을 클릭해보면

게시글 상세정보가 필드에 정상적으로 렌더링 되는 것을 확인할 수 있습니다.

스프링부트 게시판 상세보기 - seupeulingbuteu gesipan sangsebogi
게시글 작성(수정) 페이지

5. 컨트롤러에 게시글 수정 메서드 추가하기

게시글 등록 구현하기에서 게시글 수정에 대한 처리도 미리 해두었기 때문에

게시글 수정 URI와 서비스에 있는 비즈니스 로직만 연결해주면 수정 처리는 끝입니다.

PostController에 다음의 메서드를 추가해 주세요.

    // 기존 게시글 수정
    @PostMapping("/post/update.do")
    public String updatePost(final PostRequest params) {
        postService.updatePost(params);
        return "redirect:/post/list.do";
    }

6. 게시글 수정 테스트해보기

우선 기존 리스트 데이터는 다음과 같습니다.

여기서 공지 데이터의 상세 페이지로 이동해 보겠습니다.

스프링부트 게시판 상세보기 - seupeulingbuteu gesipan sangsebogi
게시글 리스트 페이지

상세 페이지로 이동했고, 수정하기 버튼을 클릭해 보겠습니다.

스프링부트 게시판 상세보기 - seupeulingbuteu gesipan sangsebogi
게시글 상세 페이지

수정 페이지로 이동했고, 공지글 설정을 해제하고 제목, 이름, 내용도 수정한 후 저장해 보도록 하겠습니다.

스프링부트 게시판 상세보기 - seupeulingbuteu gesipan sangsebogi
게시글 작성(수정) 페이지
스프링부트 게시판 상세보기 - seupeulingbuteu gesipan sangsebogi
게시글 작성(수정) 페이지 - 변경할 데이터

updatePost( )가 실행된 후에 리스트 페이지로 리다이렉트 되었습니다.

기존에 공지사항 글이었던 3번 게시글의 제목을 클릭해 보도록 하겠습니다.

스프링부트 게시판 상세보기 - seupeulingbuteu gesipan sangsebogi
게시글 리스트 페이지

상세 페이지에 업데이트된 내용이 정상적으로 출력되고 있습니다.

스프링부트 게시판 상세보기 - seupeulingbuteu gesipan sangsebogi
게시글 상세 페이지

마무리

이번에는 게시글 상세 페이지와, 게시글 수정 기능을 구현하는 시간을 가져보았습니다.

다음 글에서는 CRUD의 마지막 단계인 게시글 삭제(Delete) 기능을 구현해 보도록 하겠습니다.

오늘도 방문해 주신 여러분께 진심으로 감사드립니다!

좋은 하루 보내세요 :)


프로젝트를 import 해서 사용하신다면, application.properties에서 DB 정보만 올바르게 변경해서 사용해 주세요!


Board.zip

0.64MB