본문 바로가기

자바/스프링부트 기초

스프링부트! 게시판 만들면서 배우기! 3. 리스트 구현하기

자 저번글에서 로그인 , 회원가입 까지 했습니다!

이번에는 DB로 부터 글들을 불러와서 보여주는 부분을 만들어 보겠습니다!

 

 

레이아웃 작성하기

우선 완성된 레이아웃은 위에 메뉴가 있고 , 검색기능 , 페이징 기능까지 만들어 보겠습니다!

이번글에서 한번에 다루지는 않고 

 

글 보여주기 -> 페이징 -> 검색기능 순으로 순차적으로 적어보도록 하겠습니다.

우선 위에 메뉴바는 index.jsp에서 nav부분을 그대로 가져왔습니다. 그럼 이 부분을 중복 코드가 되겟죠? 그래서~~ 

저부분을 저희가 전 글에서 사용했던 jspf를 만들어 include 하겠습니다.

 

nav.jspf

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>   
<%@ taglib prefix="c"   uri="http://java.sun.com/jsp/jstl/core" %>
    <nav class="navbar navbar-inverse navbar-fixed-top">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="/">스프링 부트를 이용한 게시판 만들기</a>
        </div>
        <div id="navbar" class="navbar-collapse collapse">
	        <c:if test="${empty loginMember}">
	          <form class="navbar-form navbar-right" action="/member/login" method="post">
	            <div class="form-group">
	              <input autocomplete="off" name="id" type="text" placeholder="아아디를 입력해주세요" class="form-control">
	            </div>
	            <div class="form-group">
	              <input autocomplete="off" name="password" type="password" placeholder="비밀번호를 입력해주세요" class="form-control">
	            </div>
	            <button type="submit" class="btn btn-success" onclick="location.href ='/member/login'">로그인</button>
	            <button type="button" class="btn btn-success" onclick="location.href ='/member/register'">회원가입</button>
	          </form>
			</c:if>
			
			<c:if test="${!empty loginMember}">
				<form class="navbar-form navbar-right" action="/member/logout" method="post">
		            <div class="form-group">
		              <label class="loginMember">${loginMember.mid}님 접속을 환영합니다</label>
		            </div>

		            <button type="submit" class="btn btn-success">로그아웃</button>
	        	</form>
			
			
			
			</c:if>
        </div><!--/.navbar-collapse -->
      </div>
    </nav>

우선 list.jsp를 만들어서 include를 해줍니다!

우선 형태만 만들어 놓은거구요! 검색기능이랑 페이징기능은 차차 다루도록 하겠습니다.

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c"   uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
 	<%@include file="../part/head.jspf" %>
 	<link rel="stylesheet" href="/resource/list.css">
    <title>게시판</title>
</head>
<body>
	<div class="nav">
		<%@include file="../part/nav.jspf" %>
		
	</div>
	<div class="jumbotron">
	
		<div class="container">
			<h2>게시판 리스트</h2>
			<div class="header">
					<select id="type" name="choice" class="selectpicker" data-style="btn-info">
					    <option value="">항목선택</option>
					    <option value="title">제목</option>
					    <option value="contents">내용</option>
					    <option value="mid">작성자</option>
					</select>
					<input class="form-control" id="search" onkeyup="search(1)"  type="text" size="20" placeholder="검색어를 입력해주세요!">
					<button class="btn btn-primary" onclick="search(1)" type="button">검색</button>
			</div>
			
			<form action="./add">
				<table id="articleList" class="table table-hover tablestriped text-center" style="border: 1px solid;">
					<thead>
						<th>글 번호</th>
						<th>제목</th>
						<th>작성자</th>
						<th>작성일</th>
						<th>조회수</th>
					</thead>
					<c:forEach var="article" items="${list}" varStatus="st">
						<tbody>
							<tr>
								<td>${st.index+1}</td>
								<td><a href="./detail?aid=${article.aid}">${article.title}</a></td>
								
								<td>${article.mid}</td>
								<td>${article.regDate}</td>
								<td>${article.hit}</td>
							</tr>			
						</tbody>
					</c:forEach>
				</table>
				<c:if test="${!empty loginMember}">
					<button class="btn btn-primary">글 작성하기</button>
				</c:if>
			</form>
			
			<ul class="pagination" id="pagination">
				<li class="disabled">
					<a href="#">
					<span>«</span>
					</a>
				</li>
				<c:forEach begin="1" end ="${article.paginationInfo.totalPageCount}" step="1" varStatus="status">
					<li id="${status.count}">
					<a href="/article/list?currentPageNo=${status.count}" >
					${status.count}
					</a>
					</li>
				</c:forEach>
				<li>
				<a href="#">
				<span>»</span>
				</a>
				</li>
			</ul>
			
					
			<ul class="pagination" id="pagination1" style="display: none;">

			</ul>
		</div>
	</div>
	
	<script src="/js/list.js"></script>
</body>
</html>

 

뷰를 만들었으니 이제 Model(Dto)를 만들어볼까요

 

Article.java

롬북을 사용해서 setter,getter,생성자를 만들어줬구요!

package com.example.starter.dto;

import com.example.starter.pageing.CommonDto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Article extends CommonDto{

	private long aid;
	private String mid;
	private String title;
	private String contents;
	private String regDate;
	private int hit;
}

 

페이징에 필요한 클래스도 만들어줍니다! 

 

Criteria.java  - 페이징을 이용해 원하는 글 목록을 불러오기 위한 변수들을 모아놓은 클래스입니다.

 

package com.example.starter.pageing;

import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import lombok.Data;

@Data
public class Criteria {
	
	/** 현재 페이지 번호 */
	private int currentPageNo;

	/** 페이지당 출력할 데이터 개수 */
	private int recordsPerPage;

	/** 화면 하단에 출력할 페이지 사이즈 */
	private int pageSize;

	/** 검색 키워드 */
	private String searchKeyword;

	/** 검색 유형 */
	private String searchType;

	public Criteria() {
		this.currentPageNo = 1;
		this.recordsPerPage = 10;
		this.pageSize = 10;
	}

	public String makeQueryString(int pageNo) {

		UriComponents uriComponents = UriComponentsBuilder.newInstance()
				.queryParam("currentPageNo", pageNo)
				.queryParam("recordsPerPage", recordsPerPage)
				.queryParam("pageSize", pageSize)
				.queryParam("searchType", searchType)
				.queryParam("searchKeyword", searchKeyword)
				.build()
				.encode();

		return uriComponents.toUriString();
	}
}

 

PaginationInfo.java - 페이징을 계산하는 클래스 입니다.

package com.example.starter.pageing;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;

@Data
public class PaginationInfo {

	/** 페이징 계산에 필요한 파라미터들이 담긴 클래스 */
	private Criteria criteria;

	/** 전체 데이터 개수 */
	private int totalRecordCount;

	/** 전체 페이지 개수 */
	private int totalPageCount;

	/** 페이지 리스트의 첫 페이지 번호 */
	private int firstPage;

	/** 페이지 리스트의 마지막 페이지 번호 */
	private int lastPage;

	/** SQL의 조건절에 사용되는 첫 RNUM */
	private int firstRecordIndex;

	/** SQL의 조건절에 사용되는 마지막 RNUM */
	private int lastRecordIndex;

	/** 이전 페이지 존재 여부 */
	private boolean hasPreviousPage;

	/** 다음 페이지 존재 여부 */
	private boolean hasNextPage;

	public PaginationInfo(Criteria criteria) {
		if (criteria.getCurrentPageNo() < 1) {
			criteria.setCurrentPageNo(1);
		}
		if (criteria.getRecordsPerPage() < 1 || criteria.getRecordsPerPage() > 100) {
			criteria.setRecordsPerPage(10);
		}
		if (criteria.getPageSize() < 5 || criteria.getPageSize() > 20) {
			criteria.setPageSize(10);
		}

		this.criteria = criteria;
	}

	public void setTotalRecordCount(int totalRecordCount) {
		this.totalRecordCount = totalRecordCount;

		if (totalRecordCount > 0) {
			calculation();
		}
	}

	private void calculation() {

		/* 전체 페이지 수 (현재 페이지 번호가 전체 페이지 수보다 크면 현재 페이지 번호에 전체 페이지 수를 저장) */
		totalPageCount = ((totalRecordCount - 1) / criteria.getRecordsPerPage()) + 1;
		if (criteria.getCurrentPageNo() > totalPageCount) {
			criteria.setCurrentPageNo(totalPageCount);
		}

		/* 페이지 리스트의 첫 페이지 번호 */
		firstPage = ((criteria.getCurrentPageNo() - 1) / criteria.getPageSize()) * criteria.getPageSize() + 1;

		/* 페이지 리스트의 마지막 페이지 번호 (마지막 페이지가 전체 페이지 수보다 크면 마지막 페이지에 전체 페이지 수를 저장) */
		lastPage = firstPage + criteria.getPageSize() - 1;
		if (lastPage > totalPageCount) {
			lastPage = totalPageCount;
		}

		/* SQL의 조건절에 사용되는 첫 RNUM */
		firstRecordIndex = (criteria.getCurrentPageNo() - 1) * criteria.getRecordsPerPage();

		/* SQL의 조건절에 사용되는 마지막 RNUM */
		lastRecordIndex = criteria.getCurrentPageNo() * criteria.getRecordsPerPage();

		/* 이전 페이지 존재 여부 */
		hasPreviousPage = firstPage != 1;

		/* 다음 페이지 존재 여부 */
		hasNextPage = (lastPage * criteria.getRecordsPerPage()) < totalRecordCount;
	}

}

 

 

CommonDto.java - 이 클래스는 프로그램 소스를 좀 더 자바스럽게 하기 위한 클래스입니다. 자세한내용은 뒤에 작성하도록 하겠습니다!

package com.example.starter.pageing;

import lombok.Data;

@Data
public class CommonDto extends Criteria{
	
	private PaginationInfo paginationInfo;

}

 

 

 

이제 이 뷰를 사용자에게 보여주기 위해 Controller를 만들어볼까요!

controller 패키지 밑에 ArticleController를 하나 만들어줬습니다!

 

ArticleController.java

	@RequestMapping("/article/list")
	public String showList(Article article, Model model) {

		List<Article> articleList = articleService.selectArticleList(article);
		model.addAttribute("list",articleList);
		model.addAttribute("article",article);
		return "article/list";
	}

/article/list로 사용자가 접속하면 articleService.selectArticleList를 호출하여 게시물들을 list에 담아 

model 객체에 담아 사용 할 수 있도록 했습니다.

article폴더 밑에 있는 list.jsp를 반환하도록 했습니다!

여기서 model.article 은 나중에 페이징 정보를 담을 객체입니다!

 

우선 Controller를 끝났구 Service를 작성해볼까요

 

ArticleService.java

package com.example.starter.service;

import java.util.List;
import java.util.Map;

import org.springframework.web.multipart.MultipartFile;

import com.example.starter.dto.Article;
import com.example.starter.pageing.Criteria;

public interface ArticleService {


	public List<Article> selectArticleList(Article article);

}

 

인터페이스니 구현부가 있어야겠죠?

ArticleServiceImpl.java

package com.example.starter.service;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import com.example.starter.dao.ArticleDao;
import com.example.starter.dao.FileDao;
import com.example.starter.dto.Article;
import com.example.starter.pageing.Criteria;
import com.example.starter.dto.FileDto;
import com.example.starter.pageing.PaginationInfo;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class ArticleServiceImpl implements ArticleService{
	
	@Autowired
	ArticleDao articleDao;
	
	@Override
	public List<Article> selectArticleList(Article article) {
		List<Article> articleList = Collections.emptyList();
		
		int articleCount = articleDao.getTotalCount();
		PaginationInfo paginationInfo = new PaginationInfo(article);
		paginationInfo.setTotalRecordCount(articleCount);
		article.setPaginationInfo(paginationInfo);
		
		if(articleCount > 0 ) {
			articleList = articleDao.selectArticleList(article);
		}
		return articleList;
	}
	


}

 

이제 Dao를 작성해보겠습니다

 

ArticleDao.java

package com.example.starter.dao;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.annotations.Mapper;

import com.example.starter.dto.Article;
import com.example.starter.pageing.Criteria;
@Mapper // 이렇게 해주면 ArticleDao의 구현체를 마이바티스가 대신 구현해준다.
public interface ArticleDao {
	
	public List<Article> selectArticleList(Article article);
	public int getTotalCount(); //게시물 총 갯수를 가져와주는 메소드

}

 

ArticleDao.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace = "com.example.starter.dao.ArticleDao">

	<!--  페이징 구현 추가 부분 -->
	<select id ="getTotalCount" resultType="int">
	
		SELECT COUNT(*) FROM article
	
	</select>

	
	<select id="selectArticleList" parameterType="Article" resultType="Article">
		SELECT
			*
		FROM
			article
		ORDER BY
			aid DESC
		LIMIT
			#{paginationInfo.firstRecordIndex}, #{recordsPerPage}
	</select>

	
	

	
	
</mapper>

자 View, Controller, Service , Dao 까지 다 작성했습니다! 이제 DB안에 20~30개의 데이터를 insert 하신 후 article/list로 접속하시면 페이징기능까지 구현되어 보이실 겁니다! 오늘은 일단 틀을 완성시켯고 다음 글에서 

페이징에 대한 자세한 설명을 해보도록 하겠습니다