[TIL] Day40 - 답글

JIONY·2022년 10월 16일
0

TIL - Web BE - Spring Boot

목록 보기
16/20

부모글과 차수 정보로 답글의 정렬과 그룹핑 문제를 해결할 수 있다는 게 재밌었음ㅎㅎ 페이징도 그렇고 뭔가 새로운 기능을 추가하려면 과정이 길고 복잡한 거 같은데 문제를 해결하는 키 아이디어는 단순함.. 과정도 이 정도는 별 거 아니지~! 할 수 있을 정도가 되려면 이건 또 몇 번을 더 만들어봐야 할까 푸핫


답글 구조

계층형 게시판

  • 댓글보다 답글이 더 먼저 등장함(댓글은 별도의 테이블이 필요함)
  • 글과 글 사이에 답글이 달리면 번호 순 정렬이 불가해짐
    • 번호 외에 그룹, 부모글, 차수 필요


재귀 구조

  • 밑으로 계속 달릴 수 있는 구조
  • tree 구조 연상: 시작은 있지만 끝이 없는 비선형 구조

새글 규칙

  • 새글: 기존에 작성하던 게시글
  • 번호: 시퀀스
  • 그룹: 번호와 동일
    • 연속되지 않을 수 있음. 시퀀스 하나면 해결 가능한데 그룹을 연속된 번호로 하려면 시퀀스 두 개가 굳이 필요함
  • 부모글: 0
  • 차수: 0

답글 규칙

  • 먼저 작성된 글이 위에 나옴(번호로 정렬 불가)
  • 번호: 시퀀스
  • 그룹: 원본글(부모글)과 동일
  • 부모글: 부모글의 번호
  • 차수: 부모글의 차수+1


구현 방법

  • DB에 그룹, 부모글, 차수 컬럼 추가
    • BoardDto 업데이트
  • RowMapper, ResultSetExtractor 업데이트
  • 계산 방식에 따라 새글/답글 구분하도록 flow 변경 및 처리
    • 답글: 상세페이지에서 답글 버튼 클릭 > 글쓰기 화면으로 이동
    • flow상으로 보면 글쓰기 메뉴가 새글/답글 2개로 구분됨
    • 페이지를 또 만들지 않고 두 종류를 구분할 수 있는 방법은?
  • 계층별로 들여쓰기 되도록 view 수정
  • 등록에 대한 조회 기준 변경
  • 정렬
    • 목록에 대한 조회 기준 변경(tree 정렬)


DB 설계 변경

컬럼 추가

그룹

새 글을 기준으로 한 게시글 그룹

  • 만들 때부터 not null을 추가하면 안됨
    • 기존에 있던 글에도 영향을 미치기 때문
    • 데이터 모두 삭제(운영 중인 서비스에 불가능) 후에 추가하거나, 나중에 조건 추가

상위글(부모글)

답글이 작성된 원본글 번호

  • 상위글이 없을 때 null 이어야 하고, 없는 글 번호를 부모글로 가질 수 없음
    • 같은 테이블의 board_no 컬럼을 참조해야 함
    • 외래키이긴 한데 같은 테이블의 컬럼을 참조함 → 자기참조 외래키(재귀)
  • 답글이 달리면 상위글을 못지우도록 설정

차수

들여쓰기 횟수 (트리구조에서의 level or depth)

  • 지금까지 작성한 글은 모두 새 글이므로 depth 0으로 설정한 뒤에 not null 조건 추가
-- 테이블에 계층형 게시판 항목 추가
alter table board add board_group number;
alter table board add board_parent references board(board_no);
alter table board add board_depth number;

update board set board_group = board_no;
update board set board_depth = 0;
commit;

-- 이제 null이 없으니까 not null 조건 추가 가능
alter table board modify board_group not null;
alter table board modify board_depth not null;

DTO 업데이트

private int boardGroup, boardParent, boardDepth; 추가

  • boardParent는 null일 수 있지만 JDBC에서 0으로 변환해줌
  • 원래는 Integer로 저장해야 맞음

RowMapper, ResultSetExtractor 변경

//코드 추가
.boardGroup(rs.getInt("board_group"))
.boardParent(rs.getInt("board_parent"))
.boardDepth(rs.getInt("board_depth"))

정렬

  1. 그룹 내에서 새 글이 먼저 출력되어야 함
    • 시작점의 공통점 = 새 글의 공통점
      = board_parent == null && board_depth == 0
  2. 그 다음은 새 글의 답글 중 하나가 나와야 함
    1. 새 글의 board_no == 답글의 board_parent
    2. 단, board_no가 상위컬럼
    3. 만약 항목이 여러 개면 번호 오름차순 출력
    4. 오라클은 트리정렬을 지원하는 db

조회 구문 변경

select * from board \[검색 조건]

connect by prior A=B (A가 상위 컬럼)
: 번호와 상위글 번호가 같은 글 출력(글 번호가 상위 컬럼)

start with board_parent is null
: board_parent가 null인 글이 시작점

order siblings by \[정렬 조건]
: 연관된 항목들 꺼내기, 여러 개면 조건 순 정렬


적용

DaoImpl에서 검색 결과/목록 조회 메소드의 order by 절을 변경

connect by prior board_no = board_parent 
start with board_parent is null 
order siblings by board_group desc, board_no asc

들여쓰기

차수만큼 제목이 들여쓰기 되도록 view 수정

  • 차수만큼 띄어쓰기( ) 반복
  • 띄어쓰기가 차지하는 용량이 가장 작아서 이 방법을 선택함. 디자인에서 여백으로 조정할 수도 있음
<!-- list.jsp -->
<c:forEach var="i" begin="1" end="${boardDto.boardDepth}" step="1">
	&nbsp;&nbsp; 
</c:forEach>

+) 답글이면 parent != 0, depth > 0

  • 이 때, 제목 앞에 답글 아이콘 붙도록 설정 가능

글쓰기

파라미터 추가

(새)글쓰기와 답글쓰기 모두 글쓰기 매핑 주소를 사용하지만 구분이 되어야 함. boardParent를 주소에 파라미터로 추가

게시글 상세 > 글쓰기

boardParent가 주소 파라미터에 있으면 답글, 없으면 새글

<%-- detail.jsp 회원만 글쓰기/답글쓰기 --%>
<c:if test="${loginId != null}">
	<h2><a href="write">글쓰기</a></h2>
	<h2><a href="write?boardParent=${boardDto.boardNo}">답글쓰기</a></h2>
</c:if>

글쓰기 GET > POST

boardParent가 주소 파라미터에 있으면 답글

  • 부모글 번호를 추가로 전송하도록 처리, 사용자에게 보이면 안됨
  • 등록 메소드 수정 필요
<form action="write" method="post">
	<%-- 답글이라면 부모글번호를 추가로 전송하도록 처리 --%>
	<c:if test="${isReply}">
		<input type="hidden" name="boardParent" value="${param.boardParent}">
	</c:if>

Controller 확인

답글일 때 파라미터를 추가했지만, modelAttribute에 자동으로 추가되도록하기 위해서 boardDto 필드와 이름을 맞췄으므로 추가 수정 불필요


## 화면 제목 구분 새글/답글쓰기 제목 다르게 나오도록 변경
<c:set var="isReply" value="${param.boardParent != null}"></c:set>
	<c:choose>
		<c:when test="${isReply}">
			<h1>답글쓰기</h1>
		</c:when>
		<c:otherwise>
			<h1>글쓰기</h1>
		</c:otherwise>
	</c:choose>

등록 메소드 수정

새글/답글 구분해서 글번호 넣어주도록 수정 필요
답글일 때 부모글의 그룹번호, 차수 등을 추가로 더 조회해야 함

[문제점] 등록 메소드 하나에 기능이 너무 많아짐
DAO에는 통상적으로 딱 하나의 기능을 할 수 있는 메소드들만 구현함(나중에 부르기 쉬우니까)
→ 컨트롤러에서 DB 접속하도록 우선 구현한 다음에 해당 내용을 나중에 서비스로 이관할 예정


DAO 업데이트

[문제점] 그룹번호에 시퀀스 번호 넣어야 하는데 그걸 insert2에서 가져오니까 문제가 됨
→ 등록 메소드에서 시퀀스 부여 기능 분리

시퀀스 생성 메소드 추가

@Override
public int sequence() {
	String sql = "select board_seq.nextval from dual";
    int boardNo = jdbcTemplate.queryForObject(sql, int.class);
    return boardNo;
    	}

등록 메소드 수정

  • 그룹, 부모글, 차수 추가
  • board_no는 시퀀스 생성 메소드에서 받아오도록 변경
@Override
public void insert2(BoardDto boardDto) {
	String sql = "insert into board("
    			+ "board_no, board_head, board_title, board_content, "
    			+ "board_writer, board_group, board_parent, board_depth) "
    			+ "values(?, ?, ?, ?, ?, ?, ?, ?)";
    				
   Object[] param = {
    					boardDto.getBoardNo(),
    					boardDto.getBoardHead(),
    					boardDto.getBoardTitle(), 
    					boardDto.getBoardContent(),
    					boardDto.getBoardWriter(),
    					boardDto.getBoardGroup(),
    					boardDto.getBoardParent(),
    					boardDto.getBoardDepth()
    				};
    				
    jdbcTemplate.update(sql, param);
}

Controller 수정

/write에서 등록 메소드 실행 전에 번호를 미리 생성하고 새글/답글 구분하도록 처리

//번호 미리 생성
int boardNo = boardDao.sequence();
boardDto.setBoardNo(boardNo);

//등록 전에 새글/답글 구분해서 그에 맞는 계산 수행
if(boardDto.getBoardParent()==0) {//새글(int로 선언해서 null아니고 0)
	boardDto.setBoardGroup(boardNo);
    boardDto.setBoardParent(0); //기본값 어차피 0
    boardDto.setBoardDepth(0); //기본값 어차피 0
}else {//답글
	BoardDto parentDto = boardDao.selectOne(boardDto.getBoardParent());
    boardDto.setBoardGroup(parentDto.getBoardGroup());
    boardDto.setBoardDepth(parentDto.getBoardGroup()+1);
}

에러 해결

위 내용대로 수정하고 등록되는지 확인해보면 에러 발생함

  • 원인: DB에 들어갈 때는 새글의 parent가 0이 아니라 null로 들어가야함

  • 해결책: parent가 0이면 null로 바꿔서 들어가도록 수정

    • DTO에 getter 메소드 추가
    //DB에 insert할 때 0대신 null이 등록되도록 값을 변환해서 반환
    public Object getBoardParentInteger() {
        if(boardParent == 0) {
            return null;
        }else {
            return boardParent;
        }
    }
    • 등록 메소드의 param에서 .getBoardParent를 .getBoardParentInteger()로 변경

0개의 댓글