2021년 01월 06일
업데이트:
게시판 상세조회
- 게시판 목록에서 한 행을 클릭 시 상세조회
한 행 클릭 (boardList.jsp)
<!-- 하단 스크립트 작성-->
<script>
// 게시글 상세보기 기능 (jquery를 통해 작업)
$("#list-table td").on("click", function(){
// 게시글 번호 얻어오기
var boardNo = $(this).parent().children().eq(0).text();
//console.log(boardNo);
var url = "${contextPath}/board/view.do?cp=${pInfo.currentPage}&no=" + boardNo;
location.href = url;
});
</script>
BoardController.java
// 게시글 상세 조회 Controller ****************************
else if(command.equals("/view.do")) {
errorMsg = "게시글 상세 조회 과정에서 오류 발생";
int boardNo = Integer.parseInt(request.getParameter("no"));
//상세조회 비즈니스 로직 수행 후 결과 반환 받기
Board board = service.selectBoard(boardNo);
if(board != null) {// 상세조회 성공
path = "/WEB-INF/views/board/boardView.jsp";
request.setAttribute("board", board);
view = request.getRequestDispatcher(path);
view.forward(request, response);
}else {
request.getSession().setAttribute("swalIcon", "error");
request.getSession().setAttribute("swalTitle", "게시글 상세 조회 실패");
// 조회가 실패했을 경우 첫번째 페이지로 이동
response.sendRedirect("list.do?cp=1");
}
}
BoardService.java
/** 게시글 상세조회
* @param boardNo
* @return board
* @throws Exception
*/
public Board selectBoard(int boardNo) throws Exception{
Connection conn = getConnection();
Board board = dao.selectBoard(conn, boardNo);
close(conn);
return board;
}
board-query.xml
<!-- 게시글 상세 조회 -->
<entry key="selectBoard">
SELECT * FROM V_BOARD
WHERE BOARD_NO = ?
AND BOARD_STATUS = 'Y'
</entry>
BoardDAO.java
/** 게시글 상세 조회 DAO
* @param conn
* @param boardNo
* @return board
* @throws Exception
*/
public Board selectBoard(Connection conn, int boardNo) throws Exception {
Board board = null;
String query = prop.getProperty("selectBoard");
try {
pstmt = conn.prepareStatement(query);
pstmt.setInt(1, boardNo);
rset = pstmt.executeQuery();
if(rset.next()) {
board = new Board();
board.setBoardNo(rset.getInt("BOARD_NO"));
board.setBoardTitle(rset.getString("BOARD_TITLE"));
board.setBoardContent(rset.getString("BOARD_CONTENT"));
board.setMemberId(rset.getString("MEMBER_ID"));
board.setReadCount(rset.getInt("READ_COUNT"));
board.setBoardCreateDate(rset.getTimestamp("BOARD_CREATE_DT"));
board.setBoardModifyDate(rset.getTimestamp("BOARD_MODIFY_DT"));
board.setCategoryName(rset.getString("CATEGORY_NM"));
}
} finally {
close(rset);
close(pstmt);
}
return board;
}
boardView.jsp
<!-- el 형식으로 내용 채우기 -->
<h6 class="mt-4">카테고리 : [${board.categoryName }]</h6>
<h3 class="mt-4">${board.boardTitle}</h3>
<p class="lead">
작성자 : ${board.memberId }
</p>
<p>
<span class="board-dateArea">
작성일 : <fmt:formatDate value="${board.boardCreateDate}" pattern="yyyy년 MM월 dd일 HH:mm:ss"/>
<br>
마지막 수정일 : <fmt:formatDate value="${board.boardModifyDate}" pattern="yyyy년 MM월 dd일 HH:mm:ss"/>
</span>
<span class="float-right">조회수 ${board.readCount }</span>
</p>
<div id="board-content">${board.boardContent }</div>
상세조회 버튼 관련 기능
목록으로 (boardView.jsp)
<c:url var="goToList" value="list.do">
<c:param name="cp">${param.cp}</c:param>
</c:url>
<a href="${goToList}" class="btn btn-primary float-right">목록으로</a>
게시글 작성자와 로그인한 회원이 같을 시
- 로그인 회원과 작성자가 다를 시
- 로그인 회원과 작성자가 같을 시
<c:if test="${ !empty loginMember &&(board.memberId == loginMember.memberId) }">
<button id="deleteBtn" class="btn btn-primary float-right">삭제</button>
<a href="#" class="btn btn-primary float-right ml-1 mr-1">수정</a>
</c:if>
조회수 증가
BoardService.java
// 게시글 상세조회 메소드 내에 작성한다.
// 조회수 증가
if(board != null) { // DB에서 조회 성공 시
int result = dao.increaseReadCount(conn, boardNo);
if(result > 0) {
commit(conn);
// 반환되는 Board 데이터에는 조회수가 증가되어 있지 않기 떄문에
// 조회수를 1 증가 시켜줌
board.setReadCount(board.getReadCount() + 1);
}
else rollback(conn);
}
board-query.xml
<!-- 조회수 증가 -->
<entry key="increaseReadCount">
UPDATE BOARD SET
READ_COUNT = READ_COUNT + 1
WHERE BOARD_NO = ?
</entry>
BoardDAO.java
/** 조회수 증가 DAO
* @param conn
* @param boardNo
* @return result
* @throws Exception
*/
public int increaseReadCount(Connection conn, int boardNo) throws Exception {
int result = 0;
String query = prop.getProperty("increaseReadCount");
try {
pstmt = conn.prepareStatement(query);
pstmt.setInt(1, boardNo);
result = pstmt.executeUpdate();
} finally {
close(pstmt);
}
return result;
}
검색 기능
- 제목, 내용, 제목 + 내용, 작성자 검색 시
boardList.jsp
<!-- 페이징 처리 부분 주소 조건 -->
<c:choose>
<%-- 검색 내용이 파라미터에 존재할 때 == 검색을 통해 만들어진 페이지인가? --%>
<c:when test="${!empty param.sk && !empty param.sv}">
<c:url var="pageUrl" value="/search.do"/>
<%-- 쿼리 스트링으로 사용할 내용을 변수에 저장 --%>
<c:set var="searchStr" value="&sk=${param.sk}&sv=${param.sv}"/>
</c:when>
<c:otherwise>
<c:url var="pageUrl" value="/board/list.do"/>
</c:otherwise>
</c:choose>
<!-- 페이징 처리 부분 중 주소 변경 -->
<%--
검색을 안했을 때 : /board/list.do?cp=1
검색을 했을 때 : /search.do?cp=1&sk=title&sv=49
--%>
<c:set var="firstPage" value="${pageUrl}?cp=1"/>
<c:set var="lastPage" value="${pageUrl}?cp=${pInfo.maxPage}${searchStr}"/>
<%-- EL을 이용한 숫자 연산의 단점 : 연산이 자료형에 영향을 받지 않는다. ex) 5/2 = 2.5 --%>
<%-- <fmt:parseNumber> : 숫자 형태를 지정하여 변수 선언
integerOnly="true" : 정수로만 숫자 표현 (소수점 버림)
--%>
<!-- 페이징 처리 부분 중 주소 변경 -->
<c:forEach var="page" begin="${pInfo.startPage}" end="${pInfo.endPage}">
<!-- for(int page=0; page<=10; page++) 비슷하다고 생각하면 된다. -->
<c:choose>
<c:when test="${pInfo.currentPage == page}">
<li>
<a class="page-link">${page}</a>
</li>
</c:when>
<c:otherwise>
<li>
<a class="page-link" href="${pageUrl}?cp=${page}${searchStr}">${page}</a>
</li>
</c:otherwise>
</c:choose>
</c:forEach>
<fmt:parseNumber var="c1" value="${(pInfo.currentPage - 1)/10}" integerOnly="true"/>
<fmt:parseNumber var="prev" value="${c1 * 10}" integerOnly="true"/>
<c:set var="prevPage" value="${pageUrl}?cp=${prev}${searchStr}"/> <!-- /board/list/do?cp=10 -->
<fmt:parseNumber var="c2" value="${(pInfo.currentPage + 9)/10}" integerOnly="true"/>
<fmt:parseNumber var="next" value="${c2 * 10 + 1}" integerOnly="true"/>
<c:set var="nextPage" value="${pageUrl}?cp=${next}${searchStr}"/>
<div class="my-5">
<form action="${contextPath}/search.do" method="GET" class="text-center" id="searchForm">
<select name="sk" class="form-control" style="width: 100px; display: inline-block;">
<option value="title">글제목</option>
<option value="content">내용</option>
<option value="titcont">제목+내용</option>
<option value="writer">작성자</option>
</select>
<input type="text" name="sv" class="form-control" style="width: 25%; display: inline-block;">
<button class="form-control btn btn-primary" style="width: 100px; display: inline-block;">검색</button>
</form>
</div>
</div>
SearchController.java
boardList.jsp에서 form 태그 action 속성 값으로 지정된 ${contextPath}/search.do
요청을 받기위해 SearchController를 생성한다.
@WebServlet("/search.do")
public class SearchController extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String searchKey = request.getParameter("sk");
String searchValue = request.getParameter("sv");
String cp = request.getParameter("cp");
try {
SearchService service = new SearchService();
Map<String, Object> map = new HashMap<String, Object>();
map.put("searchKey", searchKey);
map.put("searchValue", searchValue);
map.put("currentPage", cp);
// 페이징 처리를 위한 데이터를 계산하고 저장하는 객체 PageInfo 얻어오기
PageInfo pInfo = service.getPageInfo(map);
// 검색 게시글 목록 조회
List<Board> bList = service.searchBoardList(map, pInfo);
// 조회된 내용과 PageInfo 객체를 request 객체에 담아서 요청 위임
String path = "/WEB-INF/views/board/boardList.jsp";
request.setAttribute("bList", bList);
request.setAttribute("pInfo", pInfo);
RequestDispatcher view = request.getRequestDispatcher(path);
view.forward(request, response);
} catch (Exception e) {
e.printStackTrace();
String path = "/WEB-INF/views/common/errorPage.jsp";
request.setAttribute("errorMsg", "검색 과정에서 오류 발생");
RequestDispatcher view = request.getRequestDispatcher(path);
view.forward(request, response);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
SearchService.java
/** 검색 내용이 포함된 페이징 처리 정보 생성 Service
* @param map
* @return map
* @throws Exception
*/
public PageInfo getPageInfo(Map<String, Object> map) throws Exception {
Connection conn = getConnection();
// 얻어온 파라미터 cp가 null이면 1, 아니면 int 형으로 파싱
map.put("currentPage",
(map.get("currentPage") == null) ? 1
: Integer.parseInt((String)map.get("currentPage")));
// 검색 조건에 따른 SQL 조건문을 조합하는 메소드 호출
String condition = createCondition(map);
// DB에서 조건을 만족하는 게시글의 수를 조회하기
int listCount = dao.getListCount(conn, condition);
// 커넥션 반환
close(conn);
// PageInfo 객체를 생성하여 반환
return new PageInfo((int)map.get("currentPage"), listCount);
}
/** 검색 조건에 따라 SQL에 사용될 조건문을 조합하는 메소드
* @param map
* @return
*/
private String createCondition(Map<String, Object> map) {
String condition = null;
String searchKey = (String)map.get("searchKey");
String searchValue = (String)map.get("searchValue");
// 검색 조건(searchKey)에 따라 SQL 조합
switch(searchKey) {
case "title" :
// " BOARD_TITLE LIKE '%' || '49' || '%' "
condition = " BOARD_TITLE LIKE '%' || '" + searchValue + "' || '%' ";
break;
case "content" :
condition = " BOARD_CONTENT LIKE '%' || '" + searchValue + "' || '%' ";
break;
case "titcont" :
condition = " (BOARD_TITLE LIKE '%' || '" + searchValue + "' || '%' "
+ "OR BOARD_CONTENT LIKE '%' || '" + searchValue + "' || '%') ";
break;
case "writer" :
condition = " MEMBER_ID LIKE '%' || '" + searchValue + "' || '%' ";
break;
}
return condition;
}
/** 검색 게시글 목록 리스트 조회 Service
* @param map
* @param pInfo
* @return bList
* @throws Exception
*/
public List<Board> searchBoardList(Map<String, Object> map, PageInfo pInfo) throws Exception {
Connection conn = getConnection();
String condition = createCondition(map);
List<Board> bList = dao.searchBoardList(conn, pInfo, condition);
close(conn);
return bList;
}
SearchDAO.java
/** 조건을 만족하는 게시글 수 조회 DAO
* @param conn
* @param condition
* @return listCount
* @throws Exception
*/
public int getListCount(Connection conn, String condition) throws Exception{
int listCount = 0;
String query = "SELECT COUNT(*) FROM V_BOARD WHERE BOARD_STATUS = 'Y' AND" + condition;
try {
stmt = conn.createStatement();
rset = stmt.executeQuery(query);
if(rset.next()) {
listCount = rset.getInt(1);
}
} finally {
close(rset);
close(stmt);
}
return listCount;
}
/** 검색 게시글 목록 조회 DAO
* @param conn
* @param pInfo
* @param condition
* @return bList
* @throws Exception
*/
public List<Board> searchBoardList(Connection conn, PageInfo pInfo, String condition) throws Exception{
List<Board> bList = null;
String query =
"SELECT * FROM" +
" (SELECT ROWNUM RNUM , V.*" +
" FROM" +
" (SELECT * FROM V_BOARD " +
" WHERE " + condition +
" AND BOARD_STATUS = 'Y' ORDER BY BOARD_NO DESC) V )" +
"WHERE RNUM BETWEEN ? AND ?";
try {
// SQL 구문 조건절에 대입할 변수 생성
int startRow = (pInfo.getCurrentPage() - 1) * pInfo.getLimit() + 1;
int endRow = startRow + pInfo.getLimit() - 1;
pstmt = conn.prepareStatement(query);
pstmt.setInt(1, startRow);
pstmt.setInt(2, endRow);
rset = pstmt.executeQuery();
bList = new ArrayList<Board>();
while(rset.next()) {
Board board = new Board(rset.getInt("BOARD_NO"),
rset.getString("BOARD_TITLE"),
rset.getString("MEMBER_ID"),
rset.getInt("READ_COUNT"),
rset.getString("CATEGORY_NM"),
rset.getTimestamp("BOARD_CREATE_DT"));
bList.add(board);
}
} finally {
close(rset);
close(pstmt);
}
return bList;
}
검색창에 작성된 값 유지하기
이 기능을 설정해주지 않으면 검색 후 특정 게시글을 선택 후 목록 페이지로 돌아오면 검색창에 검색했던 내용이 사라진다.
boardList.jsp
<script>
// 게시글 상세보기 기능 (jquery를 통해 작업)
// 아래의 메소드에 url 추가
$("#list-table td").on("click", function(){
var url = "${contextPath}/board/view.do?cp=${pInfo.currentPage}&no=" + boardNo + "${searchStr}";
// /wsp/board/view.do?cp=1&no=500
});
// 검색 내용이 있을 경우 검색창에 해당 내용을 작성해두는 기능
(function(){
var searchKey = "${param.sk}";
// 파라미터 중 sk가 있을 경우 ex)"49"
// 파라미터 중 sk가 없을 경우 ex) ""
var searchValue = "${param.sv}";
// 검색창 select의 option을 반복 접근
$("select[name=sk] > option").each(function(index, item){
// index : 현재 접근 중인 요소의 인덱스
// item : 현재 접근 중인 요소
// title title
if( $(item).val() == searchKey ){ // 제목으로 검색한 경우
$(item).prop("selected", true);
}
});
// 검색어 입력창에 searchValue 값 출력
$("input[name=sv]").val(searchValue);
})();
</script>
boardView.jsp
상세 게시글 조회 페이지에서 목록으로 돌아가도 검색했던 값이 유지되도록 한다.
<c:choose>
<c:when test="${!empty param.sk && !empty param.sv}">
<c:url var="goToList" value="../search.do">
<c:param name="cp">${param.cp}</c:param>
<c:param name="sk">${param.sk}</c:param>
<c:param name="sv">${param.sv}</c:param>
</c:url>
</c:when>
<c:otherwise>
<c:url var="goToList" value="list.do">
<c:param name="cp">${param.cp}</c:param>
</c:url>
</c:otherwise>
</c:choose>
<a href="${goToList}" class="btn btn-primary float-right">목록으로</a>
로그인 여부에 따른 글쓰기 버튼
boardList.jsp
<%-- 로그인이 되어있는 경우 --%>
<c:if test="${!empty loginMember}">
<button type="button" class="btn btn-primary float-right"
id="insertBtn" onclick="location.href = '${contextPath}/board/insertForm.do'">
글쓰기
</button>
</c:if>
게시글 작성
- 게시글 작성 페이지
BoardController.java
// 게시글 작성 화면 전환 Controller **********************************
else if(command.equals("/insertForm.do")) {
path = "/WEB-INF/views/board/boardInsert.jsp";
view = request.getRequestDispatcher(path);
view.forward(request, response);
}
boardInsert.jsp
<body>
<jsp:include page="../common/header.jsp"></jsp:include>
<div class="container my-5">
<h3>게시글 등록</h3>
<hr>
<!-- 파일 업로드를 위한 라이브러리 cos.jar 라이브러리 다운로드(http://www.servlets.com/) -->
<!--
- enctype : form 태그 데이터가 서버로 제출 될 때 인코딩 되는 방법을 지정. (POST 방식일 때만 사용 가능)
- application/x-www-form-urlencoded : 모든 문자를 서버로 전송하기 전에 인코딩 (form태그 기본값)
- multipart/form-data : 모든 문자를 인코딩 하지 않음.(원본 데이터가 유지되어 이미지, 파일등을 서버로 전송 할 수 있음.)
-->
<form action="${contextPath}/board/insert.do" method="post"
enctype="multipart/form-data" role="form" onsubmit="return boardValidate();">
<div class="mb-2">
<label class="input-group-addon mr-3 insert-label">카테고리</label>
<select class="custom-select" id="categoryCode" name="categoryCode" style="width: 150px;">
<option value="10">운동</option>
<option value="20">영화</option>
<option value="30">음악</option>
<option value="40">요리</option>
<option value="50">게임</option>
<option value="60">기타</option>
</select>
</div>
<div class="form-inline mb-2">
<label class="input-group-addon mr-3 insert-label">제목</label>
<input type="text" class="form-control" id="boardTitle" name="boardTitle" size="70">
</div>
<div class="form-inline mb-2">
<label class="input-group-addon mr-3 insert-label">작성자</label>
<h5 class="my-0" id="writer">${loginMember.memberId }</h5>
</div>
<div class="form-inline mb-2">
<label class="input-group-addon mr-3 insert-label">작성일</label>
<h5 class="my-0" id="today"></h5>
</div>
<hr>
<div class="form-inline mb-2">
<label class="input-group-addon mr-3 insert-label">썸네일</label>
<div class="boardImg" id="titleImgArea">
<img id="titleImg" width="200" height="200">
</div>
</div>
<div class="form-inline mb-2">
<label class="input-group-addon mr-3 insert-label">업로드<br>이미지</label>
<div class="mr-2 boardImg" id="contentImgArea1">
<img id="contentImg1" width="150" height="150">
</div>
<div class="mr-2 boardImg" id="contentImgArea2">
<img id="contentImg2" width="150" height="150">
</div>
<div class="mr-2 boardImg" id="contentImgArea3">
<img id="contentImg3" width="150" height="150">
</div>
</div>
<!-- 파일 업로드 하는 부분 -->
<div id="fileArea">
<input type="file" id="img0" name="img0" onchange="LoadImg(this,0)">
<input type="file" id="img1" name="img1" onchange="LoadImg(this,1)">
<input type="file" id="img2" name="img2" onchange="LoadImg(this,2)">
<input type="file" id="img3" name="img3" onchange="LoadImg(this,3)">
</div>
<div class="form-group">
<div>
<label for="content">내용</label>
</div>
<textarea class="form-control" id="boardContent" name="boardContent" rows="15" style="resize: none;"></textarea>
</div>
<hr class="mb-4">
<div class="text-center">
<button type="submit" class="btn btn-primary">등록</button>
<button type="button" class="btn btn-primary">목록으로</button>
</div>
</form>
</div>
<jsp:include page="../common/footer.jsp"></jsp:include>
<body>
파일 올리기, 이미지 미리보기
라이브러리 다운
- 홈페이지 좌측
COS File Upload Library
클릭
- 페이지 하단
cos-20.08.zip
클릭
- 다운 후 압축 풀기
\cos-20.08\lib
경로에 있는 cos.jar 복사- 아래의 경로에 붙여넣기
boardInsert.jsp
<script>
$(function(){
$("#fileArea").hide(); //#fileArea 요소를 숨김
$(".boardImg").on("click", function(){ //이미지 영역이 클릭되었응ㄹ때
//클릭한 이미지 영역 인덱스 얻어오기
var index = $(".boardImg").index(this); // 클릭된 요소가 .boardImg중 몇번째 인덱스인지 반환
//클릭된 영역 인덱스에 맞는 input file태그 클릭
$("#img" + index).click();
});
});
// 각각의 영역에 파일을 첨부 했을 경우 미리 보기가 가능하도록 하는 함수
function LoadImg(value, num) {
//value.files : 파일이 업로드되어 있으면 true
//value.files[0] : 여러 파일 중 첫번째 파일이 업로드 되어 있으면 true
if(value.files && value.files[0]) {//해당 요소에 업로드된 파일이 있을 경우
var reader = new FileReader();
// 자바스크립트 FileReader
// 웹 애플리케이션이 비동기적으로 데이터를 읽기 위하여 읽을 파일을 가리키는 File 혹은 Blob객체를 이용해 파일의 내용을 읽고 사용자의 컴퓨터에 저장하는 것을 가능하게 해주는 객체
reader.readAsDataURL(value.files[0]);
// FileReader.readAsDataURL()
// 지정된의 내용을 읽기 시작합니다. Blob완료되면 result속성 data:에 파일 데이터를 나타내는 URL이 포함 됩니다.
reader.onload = function(e){
// FileReader.onload
// load 이벤트의 핸들러. 이 이벤트는 읽기 동작이 성공적으로 완료 되었을 때마다 발생합니다.
//읽어들인 내용을 출력
$(".boardImg").eq(num).children("img").attr("src", e.target.result);
//e.target.result : 파일 읽기 동작을 성공한 요소가 읽어들인 파일 내용
}
}
}
</script>
댓글남기기