2021년 01월 06일

업데이트:


게시판 상세조회

  • 게시판 목록에서 한 행을 클릭 시 상세조회
    20210106_01


한 행 클릭 (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)

  • 20210106_03

<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>

게시글 작성자와 로그인한 회원이 같을 시

  • 로그인 회원과 작성자가 다를 시
    20210106_02

  • 로그인 회원과 작성자가 같을 시
    20210106_03

<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;
}



검색 기능

  • 제목, 내용, 제목 + 내용, 작성자 검색 시
    20210106_04


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>



게시글 작성

  • 게시글 작성 페이지
    20210106_04


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>



파일 올리기, 이미지 미리보기

20210106_05

라이브러리 다운

20210106_05
👆🏼 이미지 클릭 시 공식홈페이지로 이동

  • 홈페이지 좌측 COS File Upload Library 클릭
    20210106_06

  • 페이지 하단 cos-20.08.zip 클릭
    20210106_07

  • 다운 후 압축 풀기
  • \cos-20.08\lib 경로에 있는 cos.jar 복사
  • 아래의 경로에 붙여넣기
    20210106_08


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>

댓글남기기