ㅁ 이제 게시판 서비스
- 게시판리스트페이지_list.html
- 페이징 바의 페이지 숫자를 누를 때마다 /board/list.do를 요청한다.
page라는 key값으로 내가 요청하는 페이지 번호를 넘길 예정이다.
- 하나의 게시글에 첨부파일이 여러개일 수 있다.
ㅁ header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!-- Bootstrap 사용을 위한 CDN -->
<!-- ------------------------- -->
<style>
header{height: 50px}
header a{color:black;}
header .profile-img{width:30px;}
</style>
<script>
if('${alertMsg}' != ''){
alert('${alertMsg}');
if('${historyBackYN}' == 'Y'){
history.back();
}
}
</script>
<header class="row m-3">
<div class="col-3 d-flex justify-content-center align-items-center">
<a href="${contextPath}"><img src="${ contextPath }/resources/images/goodee_logo.png" width="100px"></a>
</div>
<div class="col-5"></div>
<div class="col-4 d-flex justify-content-center align-items-center">
<c:choose>
<c:when test="${ empty loginUser }">
<!-- case1. 로그인전 -->
<a href="${contextPath}/member/signup.do">회원가입</a> |
<a href="#" data-toggle="modal" data-target="#loginModal">로그인</a>
</c:when>
<c:otherwise>
<!-- case2. 로그인후 -->
<div>
<img class="profile-img" src="${ contextPath }<c:out value='${loginUser.profileURL}' default='/resources/images/defaultProfile.png' />">
<a href="${contextPath}/member/myinfo.do">${loginUser.userName}님</a> |
<a href="${contextPath}/member/signout.do">로그아웃</a>
</div>
</c:otherwise>
</c:choose>
</div>
</header>
<nav class="navbar m-3 navbar-expand-sm bg-dark navbar-dark d-flex justify-content-center">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="${contextPath}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">공지사항</a>
</li>
<li class="nav-item">
<a class="nav-link" href="${contextPath}/board/list.do">일반게시판</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">사진게시판</a>
</li>
</ul>
</nav>
<!-- 로그인 클릭 시 뜨는 모달 (기존에는 안보이다가 위의 a 클릭시 보임) -->
<div class="modal fade" id="loginModal">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">Login</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<form action="${contextPath}/member/signin.do" method="post">
<!-- Modal Body -->
<div class="modal-body">
<label for="userId" class="mr-sm-2">ID :</label>
<input type="text" class="form-control mb-2 mr-sm-2" placeholder="Enter ID" id="userId" name="userId" required> <br>
<label for="userPwd" class="mr-sm-2">Password:</label>
<input type="password" class="form-control mb-2 mr-sm-2" placeholder="Enter password" id="userPwd" name="userPwd" required>
</div>
<!-- Modal footer -->
<div class="modal-footer">
<button type="submit" class="btn btn-primary">로그인</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">취소</button>
</div>
</form>
</div>
</div>
</div>
- <a href="${contextPath}">
로고 이미지를 클릭하면 메인페이지로 이동되게 한다.
-<a class="nav-link" href="${contextPath}">Home</a>
nav에서 Home을 누르면 메인페이지로 이동되게 한다.
- <a class="nav-link" href="${contextPath}/board/list.do">일반게시판</a>
nav에서 일반게시판을 누르면 /spring/board/list.do가 요청되게 한다.
ㅁ BoardService
package com.br.spring.service;
import java.util.List;
import java.util.Map;
import com.br.spring.dto.BoardDto;
import com.br.spring.dto.PageInfoDto;
import com.br.spring.dto.ReplyDto;
public interface BoardService {
// 게시글 목록 조회 (페이징 처리)
int selectBoardListCount();
List<BoardDto> selectBoardList(PageInfoDto pi);
// 게시글 검색 조회 (페이징 처리)
int selectSearchListCount(Map<String, String> search);
List<BoardDto> selectSearchList(Map<String, String> search, PageInfoDto pi);
// 게시글 등록
int insertBoard(BoardDto b); // 첨부파일도 BoardDto 객체에 담아버릴 예정
// 게시글 상세 - 조회수 증가
int updateIncreaseCount(int boardNo);
// 게시글 상세 - 게시글 조회
BoardDto selectBoard(int boardNo); // 첨부파일도 BoardDto 객체에 담아버릴 예정
// 게시글 삭제
int deleteBoard(int boardNo);
// 게시글 수정 <- 다중 첨부파일 수정 (나중에 기능 구현시 함)
// 댓글 목록 조회
List<ReplyDto> selectReplyList(int boardNo);
// 댓글 등록
int insertReply(ReplyDto r);
}
ㅁ BoardServiceImpl
package com.br.spring.service;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import com.br.spring.dao.BoardDao;
import com.br.spring.dto.BoardDto;
import com.br.spring.dto.PageInfoDto;
import com.br.spring.dto.ReplyDto;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Service
public class BoardServiceImpl implements BoardService {
private final BoardDao boardDao;
@Override
public int selectBoardListCount() {
return boardDao.selectBoardListCount();
}
@Override
public List<BoardDto> selectBoardList(PageInfoDto pi) {
return boardDao.selectBoardList(pi);
}
@Override
public int selectSearchListCount(Map<String, String> search) {
return 0;
}
@Override
public List<BoardDto> selectSearchList(Map<String, String> search, PageInfoDto pi) {
return null;
}
@Override
public int insertBoard(BoardDto b) {
return 0;
}
@Override
public int updateIncreaseCount(int boardNo) {
return 0;
}
@Override
public BoardDto selectBoard(int boardNo) {
return null;
}
@Override
public int deleteBoard(int boardNo) {
return 0;
}
@Override
public List<ReplyDto> selectReplyList(int boardNo) {
return null;
}
@Override
public int insertReply(ReplyDto r) {
return 0;
}
}
ㅁ BoardDao
package com.br.spring.dao;
import java.util.List;
import org.apache.ibatis.session.RowBounds;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Repository;
import com.br.spring.dto.BoardDto;
import com.br.spring.dto.PageInfoDto;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Repository
public class BoardDao {
private final SqlSessionTemplate sqlSession;
public int selectBoardListCount() {
return sqlSession.selectOne("boardMapper.selectBoardListCount");
}
public List<BoardDto> selectBoardList(PageInfoDto pi) {
RowBounds rowBounds = new RowBounds((pi.getCurrentPage() - 1) * pi.getBoardLimit(), pi.getBoardLimit());
return sqlSession.selectList("boardMapper.selectBoardList", null, rowBounds);
}
}
- 쿼리 자체는 전체 조회다. 마이바티스의 RowBounds 객체를 이용해서 그 중에서 일부만 조회해간다.
- (pi.getCurrentPage() - 1) * pi.getBoardLimit()
이게 건너뛸 게시글 갯수다. 2번 페이지 요청이면 1*5개의 게시글을 건너뛴다.
- selectList메소드의 2번째 자리는 sql문 완성시키는거. 없으면 그냥 null.
ㅁ board-mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="boardMapper">
<resultMap id="boardResultMap" type="BoardDto">
<result column="board_no" property="boardNo" />
<result column="board_title" property="boardTitle" />
<result column="user_id" property="boardWriter" />
<result column="count" property="count" />
<result column="regist_date" property="registDt" />
<result column="attach_count" property="attachCount" />
</resultMap>
<select id="selectBoardListCount" resultType="_int">
select
count(*)
from board
where status = 'Y'
</select>
<select id="selectBoardList" resultMap="boardResultMap">
select
b.board_no
, board_title
, user_id
, count
, to_char(regist_date, 'YYYY-MM-DD') as "regist_date"
, (
select count(*)
from attachment
where ref_type = 'B'
and ref_no = b.board_no
) as "attach_count"
from board b
join member on (user_no = board_writer)
where b.status = 'Y'
order by board_no desc
</select>
</mapper>
- 상관 서브쿼리.
- 산술식, 함수 이런건 별칭을 부여해주는 것이 좋다.
ㅁ BoardDto
package com.br.spring.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
@Builder
public class BoardDto {
private int boardNo;
private String boardTitle;
private String boardWriter;
private String boardContent;
private int count;
private String registDt;
private String status;
private int attachCount;
}
- 이 게시글의 첨부파일 갯수를 담을 attachCount 필드를 추가로 만들었다.
ㅁ BoardController
package com.br.spring.controller;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.br.spring.dto.BoardDto;
import com.br.spring.dto.PageInfoDto;
import com.br.spring.service.BoardService;
import com.br.spring.util.PagingUtil;
import lombok.RequiredArgsConstructor;
@RequestMapping("/board")
@RequiredArgsConstructor
@Controller
public class BoardController {
private final BoardService boardService;
private final PagingUtil pagingUtil;
// 메뉴 바에 있는 메뉴 클릭시 /board/list.do => 1번 페이지 요청
// 페이징 바에 있는 페이지 클릭시 /board/list.do?page=xx
@GetMapping("/list.do")
public void list(@RequestParam(value="page", defaultValue="1") int currentPage, Model model) {
int listCount = boardService.selectBoardListCount();
PageInfoDto pi = pagingUtil.getPageInfoDto(listCount, currentPage, 5, 5);
List<BoardDto> list = boardService.selectBoardList(pi);
model.addAttribute("pi", pi);
model.addAttribute("list", list);
// return "board/list"; 생략해도 됨.
}
}
- page라는 key값을 뽑아서 int형에 넣으려면 파싱해서 400에러가 뜬다.
그래서 메뉴바에 있는 메뉴 클릭시에는 defaultValue="1"을 작성해서 currentPage에 1이 담기게 한다.
- Model 객체는 import org.springframework.ui.Model;
Model 객체도 내부적으로 Request 같은 객체다.
- Request에 담긴 데이터는 포워딩ㅇ된 응답 페이지에서만 쓸 수 있다.
ㅁ board 폴더에 list.jsp 생성
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
#boardList th, #boardList td:not(:nth-child(2)){text-align: center;}
#boardList>tbody>tr:hover{cursor:pointer;}
.page-link {
color: #6c757d;
background-color: #fff;
border: 1px solid #ccc;
}
.page-item.active .page-link {
z-index: 1;
color: #555;
font-weight:bold;
background-color: #f1f1f1;
border-color: #ccc;
}
.page-link:focus, .page-link:hover {
color: #000;
background-color: #fafafa;
border-color: #ccc;
}
</style>
</head>
<body>
<div class="container p-3">
<!-- Header, Nav start -->
<jsp:include page="/WEB-INF/views/common/header.jsp"/>
<!-- Header, Nav end -->
<!-- Section start -->
<section class="row m-3" style="min-height: 500px">
<div class="container border p-5 m-4 rounded">
<h2 class="m-4">게시글 목록</h2>
<br>
<!-- 로그인후 상태일 경우만 보여지는 글쓰기 버튼-->
<a class="btn btn-secondary" style="float:right" href="">글쓰기</a>
<br>
<br>
<table id="boardList" class="table table-hover" align="center">
<thead>
<tr>
<th width="100px">번호</th>
<th width="400px">제목</th>
<th width="120px">작성자</th>
<th>조회수</th>
<th>작성일</th>
<th>첨부파일</th>
</tr>
</thead>
<tbody>
<c:choose>
<c:when test="${ empty list }">
<tr>
<td colspan="6">조회된 게시글이 없습니다.</td>
</tr>
</c:when>
<c:otherwise>
<c:forEach var="b" items="${ list }">
<tr>
<td>${ b.boardNo }</td>
<td>${ b.boardTitle }</td>
<td>${ b.boardWriter }</td>
<td>${ b.count }</td>
<td>${ b.registDt }</td>
<td>${ b.attachCount > 0 ? '*' : '' }</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
</table>
<br>
<ul class="pagination d-flex justify-content-center">
<li class="page-item ${ pi.currentPage == 1 ? 'disabled' : '' }">
<a class="page-link" href="${ contextPath }/board/list.do?page=${pi.currentPage-1}">Previous</a>
</li>
<c:forEach var="p" begin="${ pi.startPage }" end="${ pi.endPage }">
<li class="page-item ${ pi.currentPage == p ? 'active' : '' }"><a class="page-link" href="${contextPath}/board/list.do?page=${ p }">${ p }</a></li>
</c:forEach>
<li class="page-item ${ pi.currentPage == pi.maxPage ? 'disabled' : '' }">
<a class="page-link" href="${ contextPath }/board/list.do?page=${pi.currentPage+1}">Next</a>
</li>
</ul>
<br clear="both"><br>
<form action="" method="get" class="d-flex justify-content-center">
<div class="select" >
<select class="custom-select" name="condition">
<option value="">작성자</option>
<option value="">제목</option>
<option value="">내용</option>
</select>
</div>
<div class="text">
<input type="text" class="form-control" name="">
</div>
<button type="submit" class="search_btn btn btn-secondary">검색</button>
</form>
</div>
</section>
<!-- Section end -->
<!-- Footer start -->
<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
<!-- Footer end -->
</div>
</body>
</html>
- class는 공백으로 구분하기 때문에 공백이 반드시 있어야 한다.
ㅁ 서버 start
- nav 바의 '일반게시판' 클릭시 게시글 목록페이지(http://localhost:8888/spring/board/list.do)로 이동된다.
그리고 2번, 3번, 1번 페이지를 클릭해봤다.
- 첫번째 페이지면 Previous 버튼이 비활성화되고, 마지막 페이지면 Next 버튼이 비활성화된다.
- 페이징 바 회색 처리는 스타일이 안 먹힌 상태다.
- css가 짜증나는게 요소르 ㄹ잘 선택해도 코드 위치나 선택자 우선순위에 따라서 스타일이 적용 안될 수 있다.
header.jsp에 부트스트랩 css가 있다. 이게 최종적으로 반영되서 파란색으로 보인거다.
스타일 태그에서 페이지 관련한것만 짤라서 header.jsp에서
부트스트랩, css 그 4줄보다 아래족에 붙여넣었따.
- 이렇게 했는데도 안되면 선택자 우선순위 때문이라 !important로 우선순위 최우선으로 하기.
=====================================
ㅁ header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!-- Bootstrap 사용을 위한 CDN -->
<!-- ------------------------- -->
<style>
header{height: 50px}
header a{color:black;}
header .profile-img{width:30px;}
.page-link {
color: #6c757d;
background-color: #fff;
border: 1px solid #ccc;
}
.page-item.active .page-link {
z-index: 1;
color: #555;
font-weight:bold;
background-color: #f1f1f1;
border-color: #ccc;
}
.page-link:focus, .page-link:hover {
color: #000;
background-color: #fafafa;
border-color: #ccc;
}
</style>
<script>
if('${alertMsg}' != ''){
alert('${alertMsg}');
if('${historyBackYN}' == 'Y'){
history.back();
}
}
</script>
<header class="row m-3">
<div class="col-3 d-flex justify-content-center align-items-center">
<a href="${contextPath}"><img src="${ contextPath }/resources/images/goodee_logo.png" width="100px"></a>
</div>
<div class="col-5"></div>
<div class="col-4 d-flex justify-content-center align-items-center">
<c:choose>
<c:when test="${ empty loginUser }">
<!-- case1. 로그인전 -->
<a href="${contextPath}/member/signup.do">회원가입</a> |
<a href="#" data-toggle="modal" data-target="#loginModal">로그인</a>
</c:when>
<c:otherwise>
<!-- case2. 로그인후 -->
<div>
<img class="profile-img" src="${ contextPath }<c:out value='${loginUser.profileURL}' default='/resources/images/defaultProfile.png' />">
<a href="${contextPath}/member/myinfo.do">${loginUser.userName}님</a> |
<a href="${contextPath}/member/signout.do">로그아웃</a>
</div>
</c:otherwise>
</c:choose>
</div>
</header>
<nav class="navbar m-3 navbar-expand-sm bg-dark navbar-dark d-flex justify-content-center">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="${contextPath}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">공지사항</a>
</li>
<li class="nav-item">
<a class="nav-link" href="${contextPath}/board/list.do">일반게시판</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">사진게시판</a>
</li>
</ul>
</nav>
<!-- 로그인 클릭 시 뜨는 모달 (기존에는 안보이다가 위의 a 클릭시 보임) -->
<div class="modal fade" id="loginModal">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<!-- Modal Header -->
<div class="modal-header">
<h4 class="modal-title">Login</h4>
<button type="button" class="close" data-dismiss="modal">×</button>
</div>
<form action="${contextPath}/member/signin.do" method="post">
<!-- Modal Body -->
<div class="modal-body">
<label for="userId" class="mr-sm-2">ID :</label>
<input type="text" class="form-control mb-2 mr-sm-2" placeholder="Enter ID" id="userId" name="userId" required> <br>
<label for="userPwd" class="mr-sm-2">Password:</label>
<input type="password" class="form-control mb-2 mr-sm-2" placeholder="Enter password" id="userPwd" name="userPwd" required>
</div>
<!-- Modal footer -->
<div class="modal-footer">
<button type="submit" class="btn btn-primary">로그인</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">취소</button>
</div>
</form>
</div>
</div>
</div>
ㅁ list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<style>
#boardList th, #boardList td:not(:nth-child(2)){text-align: center;}
#boardList>tbody>tr:hover{cursor:pointer;}
</style>
</head>
<body>
<div class="container p-3">
<!-- Header, Nav start -->
<jsp:include page="/WEB-INF/views/common/header.jsp"/>
<!-- Header, Nav end -->
<!-- Section start -->
<section class="row m-3" style="min-height: 500px">
<div class="container border p-5 m-4 rounded">
<h2 class="m-4">게시글 목록</h2>
<br>
<!-- 로그인후 상태일 경우만 보여지는 글쓰기 버튼-->
<a class="btn btn-secondary" style="float:right" href="">글쓰기</a>
<br>
<br>
<table id="boardList" class="table table-hover" align="center">
<thead>
<tr>
<th width="100px">번호</th>
<th width="400px">제목</th>
<th width="120px">작성자</th>
<th>조회수</th>
<th>작성일</th>
<th>첨부파일</th>
</tr>
</thead>
<tbody>
<c:choose>
<c:when test="${ empty list }">
<tr>
<td colspan="6">조회된 게시글이 없습니다.</td>
</tr>
</c:when>
<c:otherwise>
<c:forEach var="b" items="${ list }">
<tr>
<td>${ b.boardNo }</td>
<td>${ b.boardTitle }</td>
<td>${ b.boardWriter }</td>
<td>${ b.count }</td>
<td>${ b.registDt }</td>
<td>${ b.attachCount > 0 ? '*' : '' }</td>
</tr>
</c:forEach>
</c:otherwise>
</c:choose>
</tbody>
</table>
<br>
<ul class="pagination d-flex justify-content-center">
<li class="page-item ${ pi.currentPage == 1 ? 'disabled' : '' }">
<a class="page-link" href="${ contextPath }/board/list.do?page=${pi.currentPage-1}">Previous</a>
</li>
<c:forEach var="p" begin="${ pi.startPage }" end="${ pi.endPage }">
<li class="page-item ${ pi.currentPage == p ? 'active' : '' }"><a class="page-link" href="${contextPath}/board/list.do?page=${ p }">${ p }</a></li>
</c:forEach>
<li class="page-item ${ pi.currentPage == pi.maxPage ? 'disabled' : '' }">
<a class="page-link" href="${ contextPath }/board/list.do?page=${pi.currentPage+1}">Next</a>
</li>
</ul>
<br clear="both"><br>
<form action="" method="get" class="d-flex justify-content-center">
<div class="select" >
<select class="custom-select" name="condition">
<option value="">작성자</option>
<option value="">제목</option>
<option value="">내용</option>
</select>
</div>
<div class="text">
<input type="text" class="form-control" name="">
</div>
<button type="submit" class="search_btn btn btn-secondary">검색</button>
</form>
</div>
</section>
<!-- Section end -->
<!-- Footer start -->
<jsp:include page="/WEB-INF/views/common/footer.jsp"/>
<!-- Footer end -->
</div>
</body>
</html>