본문 바로가기

TIL

<스프링 부트 Spring boot> 게시판 구현 - 답글 기능

- 게시판 답글 기능

게시판의 답글 기능은 DB에서 게시판을 생성할 때 기본적인 컬럼에 추가적으로 답글 기능과 관련된 컬럼들을 함께 만들어주어야 한다.

*기본 컬럼 - num(번호), title(글제목), contents(글내용), writer(글쓴이), hit(조회수 - 없어도 됨), date(작성날짜 - 없어도 됨)

*답글 기능 관련 컬럼

  •  ref : 원글과 원글에 대한 답글을 하나의 그룹으로 묶어줌, 원글의 번호를 참조하여 설정됨
  • step : 원글과 답글들을 한 그룹으로 묶었을 때 해당 그룹 내의 순서
  • depth : 답글이 원글에 대한 답글일 수 있고, 답글에 대한 답글일 수 있는데 이 때 이러한 답글의 계층을 나타냄

위 내용을 바탕으로 DB에서 게시판을 생성하였다면, 프론트와 백엔드 부분을 차례로 만들면 된다.

 

 

1) 프론트엔드 부분

- 게시판 리스트를 불러오는 view

게시판 테이블의 바디 영역에 답글인 경우 구분하여 표시할 수 있도록 코드를 작성한다. '&nbsp'는 공백 문자열로, 들여쓰기처럼 보이도록 넣어주었다.

<tr>
  <th scope="row">${qnaVO.num}</th>
  <td id="re">
    <c:catch>
    	<c:forEach begin="1" end="${qnaVO.depth}">&nbsp;&nbsp;&nbsp;RE:</c:forEach>
    </c:catch>
  	<a href="./select?num=${qnaVO.num}">${qnaVO.title}</a>
  </td>
  <td>${qnaVO.writer}</td>
  <td>${qnaVO.hit}</td>
  <td>${qnaVO.date}</td>
</tr>

 

 

- 글 선택 시 보이는 view

글 선택 시에 하단에 수정, 삭제 등의 기능과 함께 답변쓰기를 누르면 원글에 대한 답변을 쓸 수 있도록 하는 태그도 추가해준다. a 링크 태그로 처리하였다.

<a href="./reply?num=${qnaVO.num}">답변쓰기</a>

 

 

- 답글 작성 view

앞에서 '답변쓰기' 클릭 시 나타는 답변 작성 view를 새로 만들어준다. Spring mvc에서 제공하는 form 태그를 이용하였으며, 작성한 내용은 완료 버튼을 클릭하면 서버로 전송된다. 여기서 가장 상단의 num은 부모글, 즉 원글의 번호를 가져온 것이다. 이것이 다음 ref, step, depth를 정하는 데에 기준이 된다.

<form:form modelAttribute="qnaVO" enctype="multipart/form-data">
  <form:hidden path="num"/> <!-- 부모글번호 -->
  <div id="title">
  	<form:input path="title" id="title" placeholder="제목을 입력하세요" />
  </div>
  <div id="contents">
 	 <form:input path="contents" id="summernote" />
  </div>
  <button id="submit" type="submit" class="btn btn-secondary btn-lg">작성완료</button>
</form:form>

 

 

2) 백엔드 부분

- Mapper.xml

sql 쿼리문을 만들어준다. mybatis를 이용하였다. 쿼리문은 총 3개를 만드는데, mybatis의 경우 업데이트 쿼리문 실행 시 충돌이 일어날 수 있어 세 번째 쿼리문을 꼭 작성해주고, 여기서 이용한 MySQL에서도 edit-preferences-SQL Editor에서 safe update란에 체크란을 꼭 해지해주어야 한다.

<insert id="setReplyInsert" parameterType="QnaVO" useGeneratedKeys="true" keyProperty="num">
  		insert into destudyqna (num, title, contents, writer, hit, date, ref, step, depth)
  		values (null, #{title}, #{contents}, #{writer}, 0, now(),
  		(select R.ref from (select * from destudyqna where num=#{num}) R),
  		(select S.step+1 from (select * from destudyqna where num=#{num}) S),
  		(select D.depth+1 from (select * from destudyqna where num=#{num}) D)
  		)
</insert>
  	
<update id="setReplyUpdate" parameterType="QnaVO">
  		update destudyqna set step=step+1
  		where ref=(select R.ref from (select ref from destudyqna where num=#{num}) R)
  		and
  		step > (select S.step from (select step from destudyqna where num=#{num}) S)
</update>
  	
<update id="setRefUpdate" parameterType="QnaVO">
  		update destudyqna set ref=#{num} where num=#{num}
</update>

위 쿼리문에서 ref, step, depth는 각각 아래와 같이 정해진다.

- ref : 원글의 num(번호)를 가져온다.

- step : 1) 원글의 ref와 같은 ref 글들을 찾는다 -> 2) 원글의 step보다 큰 수의 step을 가진 글이 있는지 찾고, 있다면 각각 step에 +1을 해준다. -> 3) 답글에는 원글의 step에서 +1을 한 값을 넣는다.

- depth : 원글의 depth에서 +1한 값을 넣는다.

 

 

- Repository

Mapper.xml의 쿼리문을 실행할 수 있도록 3개의 메서드를 작성한다.

	public int setReplyInsert(QnaVO qnaVO)throws Exception;
	public int setReplyUpdate(QnaVO qnaVO)throws Exception;
	public int setRefUpdate(QnaVO qnaVO)throws Exception;

 

 

- Service

기존에 작성해두었던 일반 글쓰기의 코드에 setRefUpdate 메서드 구문을 추가한다. 그리고 답글 기능과 관련된 메서드 구문도 추가해준다.

//글쓰기
	public int setInsert(QnaVO qnaVO) throws Exception {
		int result = qnaRepository.setInsert(qnaVO);
		result = qnaRepository.setRefUpdate(qnaVO);
		return result;
	}
    
//답글
	public int setReplyInsert(QnaVO qnaVO) throws Exception {
		int result = qnaRepository.setReplyUpdate(qnaVO);
		result = qnaRepository.setReplyInsert(qnaVO);
		return result;
	}

 

 

- Controller

최종적으로 Mapper.xml -> Repository -> Service에서 실행된 메서드의 결과를 받아 view로 보내주는 코드를 작성한다(매개변수 중 BindingResult의 경우 추후 파일업로드 기능 구현을 위해 미리 넣어주었다. 그리고 우선 오버로딩을 위해서도 필요하다!).

		@GetMapping("reply")
		public String reply(@ModelAttribute QnaVO qnaVO) throws Exception{
			return "qna/reply";
		}
		
		@PostMapping("reply")
		public String reply(QnaVO qnaVO, BindingResult bindingResult) throws Exception {
			int result = qnaService.setReplyInsert(qnaVO);
			return "redirect:../qna/list";
		}