10/14/2018

스프링 쇼핑몰 만들기 #17. 상품 소감(댓글) 구현

스프링 쇼핑몰 만들기
- 깃허브 링크

상품에 소감을 작성하는 기능은 게시판에서 댓글을 작성하는 기능과 같습니다.

쇼핑몰은 상품 페이지에 이미지가 첨부되어있는 경우가 많은데, 상품 소감을 작성하면서 페이지가 새로고침하게되면 이미지를 계속 새로 불러와서 불필요한 데이터 소모가 있습니다.

그렇기 때문에 소감 기능에는 에이젝스(Ajax)를 적용할 예정인데, 그전에 기본적인 기능부터 구현하겠습니다.

먼저 필요한 테이블을 생성합니다.

create table tbl_reply (
    gdsNum      number          not null,
    userId      varchar2(50)    not null,
    repNum      number          not null,
    repCon      varchar2(2000)  not null,
    repDate     date            default sysdate,
    primary key(gdsNum, repNum) 
);

상품 소감은 이름에서 느껴지듯 '상품에 대한 소감'입니다. 그렇기 때문에 상품 소감 테이블(tbl_reply)의 기본키는 상품 번호(gdsNum)과 소감 번호(repNum)로 되어있습니다.

상품 번호는 소감이 어느 상품에 작성되었는지 구분할 수 있고, 소감 번호는 한 상품에 작성된 소감을 구분할 수 있습니다.

create sequence tbl_reply_seq;

상품 번호(repNum)을 자동으로 생성할 시퀀스입니다.

alter table tbl_reply
    add constraint tbl_reply_gdsNum foreign key(gdsNum)
    references tbl_goods(gdsNum);
   
alter table tbl_reply
    add constraint tbl_reply_userId foreign key(userId)
    references tbl_member(userId);

소감 테이블의 상품 번호와 유저 아이디는 다른 테이블에서 참조합니다. 테이블을 참조하게되면 참조하는 테이블에 값이 없는 경우 추가되지 않도록 막을 수 있습니다.

이렇게 참조키를 사용하는 이유는, 데이터의 무결성을 위해서입니다. 어떠한 이유가 생겨서 존재하지 않는 상품 번호가 유저 아이디가 데이터 베이스에 전달 될 경우, 참조키가 있다면 데이터가 입력되는걸 차단합니다.

테이블을 생성했다면 꼭 commit; 을 해둡니다.

새로운 테이블을 생성했으니, 그에 맞는 VO를 생성합니다.

view.jsp의 상품설명 하단에 소감 영역을 추가합니다.

<div id="reply">
<section class="replyForm">
<form role="form" method="post" autocomplete="off">
댓글 폼
</form>
</section>
</c:if>

<section class="replyList">
<ol>
<li>댓글 목록</li>
</ol>   
</section>
</div>

상품 소감 바로 아래에 표시됩니다.

조건문 <c:if> 를 추가하여 로그인에 따른 출력을 나누고, 폼에는 텍스트에어리어(textarea)와 버튼을 추가합니다.

<div id="reply">

<c:if test="${member == null }">
<p>소감을 남기시려면 <a href="/member/signin">로그인</a>해주세요</p>
</c:if>

<c:if test="${member != null}">
<section class="replyForm">
<form role="form" method="post" autocomplete="off">
<div class="input_area">
<textarea name="repCon" id="repCon"></textarea>
</div>

<div class="input_area">
<button type="submit" id="reply_btn">소감 남기기</button>
</div>

</form>
</section>
</c:if>

<section class="replyList">
<ol>
<li>댓글 목록</li>
</ol>   
</section>
</div>

로그인 여부에 따라서 출력되는 모습이 다릅니다.

상품 번호를 컨트롤러에 전달하기 위해, 폼 안에 숨겨진(hidden) 인풋박스를 추가합니다.

<input type="hidden" name="gdsNum" value="${view.gdsNum}">

화면에 보이지만 않을뿐 데이터를 가지고 있는 인풋박스입니다.

소감을 추가하는 쿼리문을 테스트해봅니다.

상품 번호(gdsNum), 유저 아이디(userId), 소감 내용(repCon) 이렇게 3가지가 필요합니다. 상품 번호는 위에서 숨겨진 인풋박스에 있고, 소감 내용은 텍스트에어리어가 있습니다. 유저 아이디는 현재의 세션에서 가져오면 되겠군요.

매퍼에 쿼리를 추가합니다.

<!-- 상품 소감(댓글) 작성 -->
<insert id="registReply">
insert into tbl_reply (gdsNum, userId, repNum, repCon)
    values (#{gdsNum}, #{userId}, tbl_reply_seq.nextval, #{repCon})
</insert>

DAO와 Service를 작성합니다.

컨트롤러에도 메서드를 추가합니다. 이때 메서드의 매핑된 주소가 /view 인것에 주의합니다.

// 상품 조회 - 소감(댓글) 작성
@RequestMapping(value = "/view", method = RequestMethod.POST)
public String registReply(ReplyVO reply, HttpSession session) throws Exception {
logger.info("regist reply");

MemberVO member = (MemberVO)session.getAttribute("member");
reply.setUserId(member.getUserId());

service.registReply(reply);

return "redirect:/shop/view?n=" + reply.getGdsNum();
}

HttpSession 을 이용해 member 세션에 저장되어있는 유저 아이디를 가져올 수 있습니다.

이제 잘 작동되는지 테스트해봅니다.

소감 남기기 버튼을 클릭하면 새로고침이 되고 아무것도 변화하지 않는데, 아직 리스트쪽 코드가 미완성이기 때문입니다.

데이터는 잘 입력된걸 확인할 수 있습니다.

리스트를 만들기 위해 쿼리를 테스트해봅니다.

상품 번호를 이용하여, 해당 상품에 작성된 소감을 가져올 수 있습니다. 저는 유저 아이디가 아니라 유저 닉네임을 표시하게 하고 싶습니다.

상품 소감 테이블과 맴버 테이블을 조인하여, 유저 닉네임까지 출력할 수 있도록 쿼리를 수정했습니다.

기존 ReplyVO에서 유저 닉네임(userName)가 추가된 새로운 형태이므로, 새로운 VO를 생성합니다. ReplyVO를 복사/붙여넣기 한 뒤 ReplyListVO로 이름을 변경하고, 유저 이름(userName)를 추가한 뒤 Getter/Setter를 추가하면 됩니다.

매퍼에 쿼리를 추가합니다.

<!-- 상품 소감(댓글) 리스트 -->
<select id="replyList" resultType="com.kubg.domain.ReplyListVO">
select
    r.gdsNum, r.userId, r.repNum, r.repCon, r.repDate, m.userName
from tbl_reply r
    inner join tbl_member m
        on r.userId = m.userId
    where gdsNum = #{gdsNum}
</select>

DAO와 Service를 작성합니다.

컨트롤러의 상품 조회용 메서드에 코드를 추가합니다.

List<ReplyListVO> reply = service.replyList(gdsNum);
model.addAttribute("reply", reply);

view.jsp의 소감 리스트 부분을 수정합니다.

<section class="replyList">
<ol>
<c:forEach items="${reply}" var="reply">

<li>
    <div class="userInfo">
    <span class="userName">${reply.userName}</span>
    <span class="date"><fmt:formatDate value="${reply.repDate}" pattern="yyyy-MM-dd" /></span>
    </div>
    <div class="replyContent">${reply.repCon}</div>
  </li>
  </c:forEach>
  </ol>   
</section>

유저 닉네임과 날짜, 소감 내용이 모두 출력됩니다.

스타일을 추가합니다.

<style>
section.replyForm { padding:30px 0; }
section.replyForm div.input_area { margin:10px 0; }
section.replyForm textarea { font-size:16px; font-family:'맑은 고딕', verdana; padding:10px; width:500px;; height:150px; }
section.replyForm button { font-size:20px; padding:5px 10px; margin:10px 0; background:#fff; border:1px solid #ccc; }

section.replyList { padding:30px 0; }
section.replyList ol { padding:0; margin:0; }
section.replyList ol li { padding:10px 0; border-bottom:2px solid #eee; }
section.replyList div.userInfo { }
section.replyList div.userInfo .userName { font-size:24px; font-weight:bold; }
section.replyList div.userInfo .date { color:#999; display:inline-block; margin-left:10px; }
section.replyList div.replyContent { padding:10px; margin:20px 0; }
</style>

소감 작성 폼, 작성자, 작성일, 내용이 잘 구분되며 깔끔하게 표시됩니다.

게시물 수정
  1. 안녕하세요 ㅎ 일단 공부 많이되고있습니다 !!
    시퀸스 테이블 따로 만드셨는데 전 지금 mysql을 사용중이라 auto increment 로 설정해도 무방한가요??
    설정하려면 컬럼 위치가 바뀌는데 상관없는건가요?

    답글삭제
    답글
    1. 안녕하세요? 방문해주셔서 감사합니다.

      본문에서 시퀀스를 사용하는 컬럼을 auto increment로 설정하시면 됩니다

      삭제