Kim VamPa

[Spring][쇼핑몰 프로젝트][19] 상품목록 기능 구현 본문

스프링 프레임워크/쇼핑몰 프로젝트

[Spring][쇼핑몰 프로젝트][19] 상품목록 기능 구현

Kim VamPa 2021. 4. 13. 16:08
728x90
반응형
프로젝트 Github : https://github.com/sjinjin7/Blog_Project
프로젝트 포스팅 색인(index) : https://kimvampa.tistory.com/188

목표

상품 목록 페이지 구현

 상품 목록 페이지에 구현되어야 할 '페이징화', '검색' 등을 적용할 것입니다. 이는 앞서 작가 목록 포스팅에서 구현해본 기능들이기 때문에 작성 위주로 빠르게 진행하겠습니다. (코드만 있어서 이해가 잘 안되신다면 작가 목록 포스팅(1~4)을 참고 부탁드립니다.)

 

 검색 기능의 경우 '책 제목'을 통한 검색 기능 구현만 하겠습니다. 다른 여러 옵션을 통한 구현은 차후 일반 사용자의 검색에서 구현할 것입니다.

 

 

 

순서

1. Mapper 메서드

2. Service 메서드

3. Controller 

4. View 처리

 

 

 

1.  Mapper 메서드

 상품 목록 페이지에서 기본적으로 필요로한 쿼리 문과 이를 호출하는 Mapper 메서드를 먼저 작업합니다.

 

 Mapper작업을 하기전 페이징 기능 구현이 제대로 되는지 확인하기 위해, 아무 의미 없는 행들을 추가해줍니다. 저는 아래의 재귀 복사를 통하여 행들을 추가해주었습니다.

 

--재귀 복사
insert into vam_book(bookName, authorId, publeYear, publisher, cateCode, bookPrice, bookStock, bookDiscount,bookIntro, bookContents)
(select bookName, authorId, publeYear, publisher, cateCode, bookPrice, bookStock, bookDiscount,bookIntro, bookContents from vam_book);

 

그림 1-1 Oracle

 

그림 1-2 MySQL

 

 

 

1-1 AdminMapper.java

 

 AdminMapper.java 인터페이스에 아래 두 개의 메서드를 추가합니다. 'goodsGetList()' 메서드는 각각 상품 목록 페이지에 출력될 페이징화 된 상품 데이터입니다. goodsGetTotal()는 페이지 이동 인터페이스 객체를 인스턴스화 하는데 필요로 한 '상품 목록 총 개수' 데이터입니다.

 

 

	/* 상품 리스트 */
	public List<BookVO> goodsGetList(Criteria cri);
	
	/* 상품 총 개수 */
	public int goodsGetTotal(Criteria cri);

 

 

1-2 AdminMapper.xml

 

 '상품 목록(상품 관리)' 페이지에 출력시킬 열들의 정보는 'bookId', 'bookName', 'authorName',   'cateName', 'bookPrice', 'bookStock'입니다. 문제는 'bookName', 'cateName' 열의 정보는 "vam_book" 테이블에 있지 않습니다.

 

 따라서 같이 출력 되록해주기 위해선 '서브 쿼리' 혹은 '조인'을 사용해야 합니다. 저는 Oracle에서는 서브 쿼리를 MySQL에는 '조인'을 사용하였습니다. (Oracle에서도 조인을 사용하고자 했지만 제가 의도한 대로 출력되지 않아 서브 쿼리를 사용하였습니다.)

 

 아래는 AmdinMapper.xml에 작성한 코드입니다.

 

Oracle

 

※ 인덱스 힌트를 사용하기 위해서 vam_book의 인덱스명을 아래의 명령을 통해 찾았습니다. (Oralce의 경우 삽입 후 반드시 'commit' 명령을 실행해주어야 합니다.)

SELECT * FROM USER_INDEXES WHERE TABLE_NAME = 'VAM_BOOK';

 

  	<!-- 상품 리스트 -->
  	<select id="goodsGetList" resultType="com.vam.model.BookVO">
  	
	  	<![CDATA[
		select * from(
		    select /*+INDEX_DESC(vam_book SYS_C007551)*/ rownum as rn, bookId, bookName,
		        (select authorName from vam_author where vam_book.authorId = vam_author.authorId) authorName,
		        (select cateName from vam_bcate where vam_book.cateCode = vam_bcate.cateCode) cateName,bookStock,regDate
		    from vam_book 
		    where 
		]]>    
		
		    <if test="keyword != null">
		    
			    bookName like '%' || #{keyword} || '%' and
			    
		    </if>
		     
		<![CDATA[    
		    rownum <= #{pageNum} * #{amount} 
		    )
		where rn > (#{pageNum} -1) * #{amount}		  	
  		]]>
  	
  	</select>
  	
  	<!-- 상품 총 개수 -->
  	<select id="goodsGetTotal" resultType="int">
  	
  		select count(*) from vam_book
  		
  		<if test="keyword != null">
  		
  			where bookName like '%' || #{keyword} || '%'
  		</if>
  		
  	</select> 

 

 

MySQL

 

	<!-- 상품 리스트 -->
	<select id="goodsGetList" resultType="com.vam.model.BookVO">
	
		select  bookId, bookName,  b.authorName, c.cateName, bookStock, a.regDate
		from vam_book a 
		left outer join vam_author b on a.authorId = b.authorId
		left outer join vam_bcate c on a.cateCode = c.cateCode
		<if test="keyword != null">
			where (bookName like concat ('%', #{keyword}, '%'))
		</if>
		order by bookId desc 
		limit #{skip}, #{amount}		
	
	</select>
  	
  	<!-- 상품 총 갯수 -->
  	<select id="goodsGetTotal" resultType="int">
  	
  		select count(*) from vam_book
  		
  		<if test="keyword != null">
  		
  			where bookName like concat('%' , #{keyword}, '%')
  		
  		</if>
  	
  	</select>

 

 

1-3 AdminMapperTests.java

 

 작성한 Mapper 2개의 메서드를 아래의 코드를 통해서 테스트합니다.

 

	/* 상품 리스트 & 상품 총 갯수 */
	@Test
	public void goodsGetListTest() {
		
		Criteria cri = new Criteria();
		
		/* 검색조건 */
		cri.setKeyword("테스트");
		
		/* 검색 리스트 */
		List list = mapper.goodsGetList(cri);
		for(int i = 0; i < list.size(); i++) {
			System.out.println("result......." + i + " : " + list.get(i));
		}
		
		/* 상품 총 갯수 */
		int result = mapper.goodsGetTotal(cri);
		System.out.println("resutl.........." + result);
		
		
	}

 

그림 1-3

 

그림 1-4

 

 

2. Service

 AdminController.java 에서 위에서 작성한 메서드를 실행 할 수 있도록 연결해주는 Service단계의 메서드를 작성합니다.

 

 AdminService.java 인터페이스에 아래의 메서드를 추가해줍니다.

 

	/* 상품 리스트 */
	public List<BookVO> goodsGetList(Criteria cri);
	
	/* 상품 총 개수 */
	public int goodsGetTotal(Criteria cri);	

 

그림 2-1

 

 

 

AdminServiceImpl.java 클래스에 인터페이스에서 작성한 메서드를 오버라이드 구현부를 작성해줍니다.

 

	/* 상품 리스트 */
	@Override
	public List<BookVO> goodsGetList(Criteria cri) {
		log.info("goodsGetTotalList()..........");
		return adminMapper.goodsGetList(cri);
	}

	/* 상품 총 갯수 */
	public int goodsGetTotal(Criteria cri) {
		log.info("goodsGetTotal().........");
		return adminMapper.goodsGetTotal(cri);
	}	

 

그림 2-2

 

 

3. Controller

 기존의 '상품 관리(상품목록)' 페이지 이동 URL("/goodsManage") 매핑 메서드에 Criteria, model 파라미터를 추가해줍니다.

 

	/* 상품 관리(상품목록) 페이지 접속 */
	@RequestMapping(value = "goodsManage", method = RequestMethod.GET)
	public void goodsManageGET(Criteria cri, Model model) throws Exception{
	

	}

 

그림 3-1

 

 구현부에는 '상품 목록 데이터', '페이지 인터페이스 데이터'를 뷰(View)에 전송하는 아래의 코드를 추가합니다. (작가 목록 페이지 메서드와 동일합니다.)

 

		/* 상품 리스트 데이터 */
		List list = adminService.goodsGetList(cri);
		
		if(!list.isEmpty()) {
			model.addAttribute("list", list);
		} else {
			model.addAttribute("listCheck", "empty");
			return;
		}
		
		/* 페이지 인터페이스 데이터 */
		model.addAttribute("pageMaker", new PageDTO(cri, adminService.goodsGetTotal(cri)));

 

그림 3-2

 

 

뷰로 데이터가 넘어오는지 확인하기 위해서 "goodsManage.jsp"에 아래의 아래와 같이 코드를 삽입하여 확인하였습니다.

 

그림 3-3

 

그림 3-4

 

 넘어오는 것을 확인하였다면 지워줍니다.

 

 

4. View 처리

  앞서 테스트를 위해 코드를 삽입하였던 공간에 아래의 코드를 추가해주었습니다. 

 

                    <div class="goods_table_wrap">
                    	<!-- 상품 리스트 O -->
	                    <c:if test="${listcheck != 'empty'}">
	                    	<table class="goods_table">
	                    		<thead>
	                    			<tr>
										<td class="th_column_1">상품 번호</td>
	                    				<td class="th_column_2">상품 이름</td>
	                    				<td class="th_column_3">작가 이름</td>
	                    				<td class="th_column_4">카테고리</td>
	                    				<td class="th_column_5">재고</td>
	                    				<td class="th_column_6">등록날짜</td>
	                    			</tr>
	                    		</thead>	
	                    		<c:forEach items="${list}" var="list">
	                    		<tr>
	                    			<td><c:out value="${list.bookId}"></c:out></td>
	                    			<td><c:out value="${list.bookName}"></c:out></td>
	                    			<td><c:out value="${list.authorName}"></c:out></td>
	                    			<td><c:out value="${list.cateName}"></c:out></td>
	                    			<td><c:out value="${list.bookStock}"></c:out></td>
	                    			<td><fmt:formatDate value="${list.regDate}" pattern="yyyy-MM-dd"/></td>
	                    		</tr>
	                    		</c:forEach>
	                    	</table>
	                    </c:if>
	                    <!-- 상품 리스트 X -->
                		<c:if test="${listCheck == 'empty'}">
                			<div class="table_empty">
                				등록된 작가가 없습니다.
                			</div>
                		</c:if> 
                	</div>
                	
                	<!-- 검색 영역 -->
                	<div class="search_wrap">
                		<form id="searchForm" action="/admin/goodsManage" method="get">
                			<div class="search_input">
                    			<input type="text" name="keyword" value='<c:out value="${pageMaker.cri.keyword}"></c:out>'>
                    			<input type="hidden" name="pageNum" value='<c:out value="${pageMaker.cri.pageNum }"></c:out>'>
                    			<input type="hidden" name="amount" value='${pageMaker.cri.amount}'>
                    			<input type="hidden" name="type" value="G">
                    			<button class='btn search_btn'>검 색</button>                				
                			</div>
                		</form>
                	</div>
                	
                	<!-- 페이지 이름 인터페이스 영역 -->
                	<div class="pageMaker_wrap">
                		<ul class="pageMaker">
                			
                			<!-- 이전 버튼 -->
                			<c:if test="${pageMaker.prev }">
                				<li class="pageMaker_btn prev">
                					<a href="${pageMaker.pageStart -1}">이전</a>
                				</li>
                			</c:if>
                			
                			<!-- 페이지 번호 -->
                			<c:forEach begin="${pageMaker.pageStart }" end="${pageMaker.pageEnd }" var="num">
                				<li class="pageMaker_btn ${pageMaker.cri.pageNum == num ? 'active':''}">
                					<a href="${num}">${num}</a>
                				</li>	
                			</c:forEach>
                		
	                    	<!-- 다음 버튼 -->
	                    	<c:if test="${pageMaker.next}">
	                    		<li class="pageMaker_btn next">
	                    			<a href="${pageMaker.pageEnd + 1 }">다음</a>
	                    		</li>
	                    	</c:if>
	                    </ul>
                	</div>
                	
                	<form id="moveForm" action="/admin/goodsManage" method="get" >
 						<input type="hidden" name="pageNum" value="${pageMaker.cri.pageNum}">
						<input type="hidden" name="amount" value="${pageMaker.cri.amount}">
						<input type="hidden" name="keyword" value="${pageMaker.cri.keyword}">
                	</form>

 

그림 4-1

 

그림 4-2

 

 추가한 태그들에 css 설정을 추가해주기 위해서 'goodsManage.css'에 아래의 코드를 추가해주었습니다.

 

/* 상품 목록 영역 */
.goods_table_wrap{
	padding: 20px 35px
}
.goods_table{
	width: 100%;
    border: 1px solid #d3d8e1;
    text-align: center;
    border-collapse: collapse;
}
.goods_table td{
	padding: 10px 5px;
	border : 1px solid #e9ebf0;
}
.goods_table thead{
	background-color: #f8f9fd;	
	font-weight: 600;
}
.goods_table a{
	color:#1088ed;
	font-weight: 500;
}
.th_column_1{
	width:120px;
}
.th_column_3{
	width:110px;
}
.th_column_4{
	width:140px;
}
.th_column_5{
	width:140px;
}
.th_column_6{
	
}
	
.table_empty{
	height: 50px;
    text-align: center;
    margin: 200px 0 215px 0px;
    font-size: 25px;
}

	/* 검색 영역 */
.search_wrap{
	margin-top:15px;
}
.search_input{
    position: relative;
    text-align:center;	
}
.search_input input[name='keyword']{
	padding: 4px 10px;
    font-size: 15px;
    height: 20px;
    line-height: 20px;
}
.search_btn{
	height: 32px;
    width: 80px;
    font-weight: 600;
    font-size: 18px;
    line-height: 20px;
    position: absolute;
    margin-left: 15px;
    background-color: #c3daf7;
}


	/* 페이지 버튼 인터페이스 */
.pageMaker_wrap{
	text-align: center;
    margin-top: 30px;
    margin-bottom: 40px;
}
.pageMaker{
    list-style: none;
    display: inline-block;
}	
.pageMaker_btn {
    float: left;
    width: 40px;
    height: 40px;
    line-height: 40px;
    margin-left: 20px;
}
.active{
	border : 2px solid black;
	font-weight:400;
}
.next, .prev {
    border: 1px solid #ccc;
    padding: 0 10px;
}
.pageMaker_btn a:link {color: black;}
.pageMaker_btn a:visited {color: black;}
.pageMaker_btn a:active {color: black;}
.pageMaker_btn a:hover {color: black;}
.next a, .prev a {
    color: #ccc;
}

 

 

 위의 코드들을 추가 해준 후 서버를 구동시켜 해당 페이지를 들어가면 아래와 같이 출력됩니다.

 

그림 4-3

 

 아래의 페이지 이동 인터페이스 동작하도록 만들기 위해서 <script> 태그에 아래의 JS코드를 추가해줍니다.

 

let searchForm = $('#searchForm');
let moveForm = $('#moveForm');

/* 작거 검색 버튼 동작 */
$("#searchForm button").on("click", function(e){
	
	e.preventDefault();
	
	/* 검색 키워드 유효성 검사 */
	if(!searchForm.find("input[name='keyword']").val()){
		alert("키워드를 입력하십시오");
		return false;
	}
	
	searchForm.find("input[name='pageNum']").val("1");
	
	searchForm.submit();
	
});


/* 페이지 이동 버튼 */
$(".pageMaker_btn a").on("click", function(e){
	
	e.preventDefault();
	
	moveForm.find("input[name='pageNum']").val($(this).attr("href"));
	
	moveForm.submit();
	
});

 

그림 4-5

 

 위의 코드를 추가 해준 뒤 검색, 페이지 이동 인터페이스 정상적으로 동작하는지 테스트합니다.

 

그림 4-6

 

그림 4-7

 

 

REFERENCE

 

DATE

  • 2020.04.13
728x90
반응형
Comments