[Framework] TIL 070 - 23.10.27

유진·2023년 10월 26일
0

07_Framework

게시판 검색창

  • pagination 검색결과에 따라 바뀌어야 함.

boardList.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"  %>


<%-- map에 저장된 값들을 각각 변수에 저장 --%>
<c:set var="pagination" value="${map.pagination}" />
<c:set var="boardList" value="${map.boardList}" />

<c:set var="boardName" value="${boardTypeList[boardCode-1].BOARD_NAME}"/>



<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>${boardName}</title>

    <link rel="stylesheet" href="/resources/css/board/boardList-style.css">

</head>
<body>
    <main>
        <jsp:include page="/WEB-INF/views/common/header.jsp"/>
        
        <c:if test="${not empty param.key}"> <!-- param: EL구문 내장객체 -->
        	<c:set var="kq" value="&key=${param.key}&query=${param.query}" />
        </c:if>

        
        <section class="board-list">

            <h1 class="board-name">${boardName}</h1>
            
            <c:if test="${not empty param.query}">
            	<h3 style="margin:30px">"${param.query}" 검색 결과</h3>
			</c:if>

            <div class="list-wrapper">
                <table class="list-table">
                    
                    <thead>
                        <tr>
                            <th>글번호</th>
                            <th>제목</th>
                            <th>작성자</th>
                            <th>작성일</th>
                            <th>조회수</th>
                            <th>좋아요</th>
                        </tr>
                    </thead>

                    <tbody>
                    	<c:choose>
                    		<%-- 조회된 게시글 목록이 비어있거나 null 경우 --%>
                    		<c:when test="${empty boardList}">
                    			<!-- 게시글 목록 조회 결과가 비어있다면 -->
		                         <tr>
		                             <th colspan="6">게시글이 존재하지 않습니다.</th>
		                         </tr>
                    		</c:when>
                    		
                    		<c:otherwise>
                    		
                    			<c:forEach items="${boardList}" var="board">
                    				<!-- 게시글 목록 조회 결과가 있다면 -->
			                        <tr>
			                            <td>${board.boardNo}</td>
			                            <td> 
			                            
			                            	<%-- 썸네일이 있을 경우 --%>
			                            	<c:if test="${not empty board.thumbnail}">
			                                	<img class="list-thumbnail" src="${board.thumbnail}">
											</c:if>
			
											<%-- ${boardCode} : @Pathvariable 로 request scope에 추가된 값 --%>
			                                <a href="/board/${boardCode}/${board.boardNo}?cp=${pagination.currentPage}">${board.boardTitle}</a>   
			                                [${board.commentCount}]                        
			                            </td>
			                            <td>${board.memberNickname}</td>
			                            <td>${board.boardCreateDate}</td>
			                            <td>${board.readCount}</td>
			                            <td>${board.likeCount}</td>
			                        </tr>
                    			
                    			</c:forEach>
                    		
                    		</c:otherwise>
                    	</c:choose>
                    	
                    
                    </tbody>
                </table>
            </div>


            <div class="btn-area">

				<!-- 로그인 상태일 경우 글쓰기 버튼 노출 -->
				<c:if test="${not empty loginMember}">
                	<button id="insertBtn">글쓰기</button>                     
				</c:if>
				
            </div>


            <div class="pagination-area">


                <ul class="pagination">
                
                    <!-- 첫 페이지로 이동 -->
                    <li><a href="/board/${boardCode}?cp=1${kq}">&lt;&lt;</a></li>

                    <!-- 이전 목록 마지막 번호로 이동 -->
                    <li><a href="/board/${boardCode}?cp=${pagination.prevPage}${kq}">&lt;</a></li>

					
                    <!-- 특정 페이지로 이동 -->
                    <c:forEach var="i" begin="${pagination.startPage}" end="${pagination.endPage}" step="1">
                    	<c:choose>
                    		<%-- 현재 보고있는 페이지 --%>
                    		<c:when test="${i == pagination.currentPage}">
                    			<li><a class="current">${i}</a></li>
                    		</c:when>
                    		
                    		<%-- 현재 페이지를 제외한 나머지 --%>
                    		<c:otherwise>
                    			<li><a href="/board/${boardCode}?cp=${i}${kq}">${i}</a></li>
                    		</c:otherwise>
                    	</c:choose>
                    </c:forEach>
                   
                    <!-- 다음 목록 시작 번호로 이동 -->
                    <li><a href="/board/${boardCode}?cp=${pagination.nextPage}${kq}">&gt;</a></li>

                    <!-- 끝 페이지로 이동 -->
                    <li><a href="/board/${boardCode}?cp=${pagination.maxPage}${kq}">&gt;&gt;</a></li>

                </ul>
            </div>


			<!-- 검색창 --> 
			            <!-- 상대주소 -->
            <form action="${boardCode}" method="get" id="boardSearch">

                <select name="key" id="searchKey">
                    <option value="t">제목</option>
                    <option value="c">내용</option>
                    <option value="tc">제목+내용</option>
                    <option value="w">작성자</option>
                </select>

                <input type="text" name="query"  id="searchQuery" placeholder="검색어를 입력해주세요.">

                <button>검색</button>
            </form>

        </section>
    </main>
    
    
    <!-- 썸네일 클릭 시 모달창 출력 -->
    <div class="modal">
        <span id="modalClose">&times;</span>
        <img id="modalImage" src="/resources/images/user.png">
    </div>


    <jsp:include page="/WEB-INF/views/common/footer.jsp"/>
    
    <script src="/resources/js/board/boardList.js"></script>

</body>
</html>

BoardController.java

package edu.kh.project.board.controller;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.service.BoardService;
import edu.kh.project.member.model.dto.Member;

@SessionAttributes({"loginMember"})
@RequestMapping("/board")
@Controller
public class BoardController {
	
	@Autowired
	private BoardService service;
	
	/*  목록 조회 : /board/1?cp=1 (cp: current page(현재페이지))
	 *  상세 조회 : /board/1/1500?cp=1
	 *  
	 *  ** 컨트롤러 따로 생성 **
	 *  삽입 : /board2/1/insert
	 *  수정 : /board2/1/1500/update
	 *  삭제 : /board2/1/1500/delete
	 * */
	
	/*
	 * ******** @PathVariable 사용 시 문제점과 해결법 ********
	 * 
	 * 문제점 : 요청 주소와 @PathVariable로 가져다 쓸 주소와 레벨이 같다면
	 * 		구분하지 않고 모두 매핑되는 문제가 발생
	 * 
	 * 해결방법 : @PathVariable 지정 시 정규 표현식 사용
	 * {키:정규표현식}
	 * 
	 * 
	 * @PathVariable : URL 경로에 있는 값을 매개변수로 이용할 수 있게하는 어노테이션
	 * + request scope에 세팅
	 * 
	 * /board/1    /board?code=1 -> 용도의 차이점이 존재
	 * 
	 * - 자원(resource) 구분/식별
	 * ex) github.com/cmhinst
	 * ex) github.com/testUser
	 * ex) /board/1 -> 1번(공지사항) 게시판
	 * ex) /board/2 -> 2번(자유 게시판) 게시판
	 * 
	 * query string 을 사용하는 경우            -> 부가적인 옵션이라고 생각하기!
	 * - 검색, 정렬, 필터링
	 * ex) search.naver.com?query=날씨
	 * ex) search.naver.com?query=종로맛집
	 * ex) /board2/insert?code=1
	 * ex) /board2/insert?code=2
	 * -> 삽입이라는 공통된 동작 수행
	 * 		단, code에 따라 어디에 삽입할지 지정(필터링)
	 * 
	 * ex) /board/list?order=recent (최신순)
	 * ex) /board/list?order=most   (인기순)
	 * 
	 * */
	
	// 게시글 목록 조회
	@GetMapping("/{boardCode:[0-9]+}") // boardCode는 1자리 이상 숫자
									   // @PathVariable : 주소를 값 자체로 쓸 수 있는 것
	public String selectBoardList( @PathVariable("boardCode") int boardCode,
								@RequestParam(value="cp", required = false, defaultValue = "1") int cp,
								Model model, // 데이터 전달용 객체
								@RequestParam Map<String, Object> paramMap // 파라미터 전부 다 담겨있음(검색 시)  // {"key":"t", "query":"test"}
							) {
		
		// boardCode 확인
		//System.out.println("boardCode : " + boardCode);
		
		if( paramMap.get("key") == null ) { // 검색어가 없을 때 (검색 X)
			
			// 게시글 목록 조회 서비스 호출
			Map<String, Object> map = service.selectBoardList(boardCode, cp);
			
			// 조회 결과를 request scope에 세팅 후 forward
			model.addAttribute("map", map);
			
		} else { // 검색어가 있을 때 (검색 O)
			
			paramMap.put("boardCode", boardCode); // paramMap = key, query, boardCode
			
			Map<String, Object> map = service.selectBoardList(paramMap, cp); // 오버로딩 적용
			
			model.addAttribute("map", map);
		}
		
		return "board/boardList";
	}
	
	
	// @PathVariable : 주소에 지정된 부분을 변수에 저장
	//				 + request scope 세팅
	
	// 게시글 상세 조회  //   /board/1/1500
	@GetMapping("/{boardCode}/{boardNo}")
	public String boardDetail(
				@PathVariable("boardCode") int boardCode,
				@PathVariable("boardNo") int boardNo,
				Model model, // 데이터 전달용 객체
				RedirectAttributes ra, // 리다이렉트 시 데이터 전달 객체
				@SessionAttribute(value="loginMember", required = false) Member loginMember,
				// 세션에서 loginMember를 얻어오는데 없으면 null, 있으면 회원정보 저장
				
				// 쿠키를 이용한 조회 수 증가에서 사용
				HttpServletRequest req,
				HttpServletResponse resp
				) throws ParseException {
		
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("boardCode", boardCode);
		map.put("boardNo", boardNo);
		
		// 게시글 상세 조회 서비스 호출
		Board board = service.selectBoard(map);
		
		String path = null;
		
		if(board != null) { // 조회 결과가 있을 경우
			
			// ----------------------------------------------------------
			// 현재 로그인 상태인 경우
			// 로그인한 회원이 해당 게시글에 좋아요를 눌렀는지 확인
			
			// 로그인 상태인 경우
			if(loginMember != null) {
				// 회원번호를 얻어와야해요
				// map(boardCode, boardNo, memberNo)
				map.put("memberNo", loginMember.getMemberNo());
				
				
				// 좋아요 여부 확인 서비스 호출
				int result = service.boardLikeCheck(map);
				
				// 결과값을 통해 분기처리
				// 누른적이 있을 경우 처리
				// "likeCheck"
				if(result > 0) model.addAttribute("likeCheck", "on"); // 누른적이 있을 경우 "likeCheck" 키 값에 "on" 값을 넣겠다.
				
				
			}
			
			// ---------------------------------------------------------
			
			// 쿠키를 이용한 조회 수 증가 처리
			
			// 1) 비회원(로그인 안한 상태인 사람) 또는 로그인한 회원의 글이 아닌경우
			if(loginMember == null ||
					loginMember.getMemberNo() != board.getMemberNo() ) {
				
				// 2) 쿠키 얻어오기
				Cookie c = null;
				
				// 요청에 담겨있는 모든 쿠키 얻어오기
				Cookie[] cookies = req.getCookies(); // Cookies ['아이디 저장', '다른 쿠키', ..., 'readBoardNo']
				
				if(cookies != null) { // 쿠키가 존재할 경우
					
					// 쿠키 중 "readBoardNo" 라는 쿠키를 찾아서 c에 대입
					for(Cookie cookie : cookies) {
						if(cookie.getName().equals("readBoardNo")) {
							c = cookie;
							break;
						}
					}
					
				}
				
				
				// 3) 기존 쿠키가 없거나 ( c == null )
				//	  존재는 하나 현재 게시글 번호가
				//	  쿠키에 저장되지 않은 경우  ( 해당 게시글 본적 없음 )
				
				// 결과 저장용 변수 선언
				int result = 0;
				
				if(c == null) {
					// 쿠키가 존재 X -> 하나 새로 생성
					c = new Cookie("readBoardNo", "|" + boardNo + "|");
					
					// 조회수 증가 서비스 호출
					result = service.updateReadCount(boardNo);
				} else {
					// 현재 게시글 번호가 쿠키에 있는지 확인
					
					// Cookie.getValue() : 쿠키에 저장된 모든 값을 읽어옴 -> String으로 반환
					
					// String.indexOf("문자열")
					// : 찾는 문자열이 String 몇번 인덱스에 존재하는지 반환
					// 단, 없으면 -1 반환
					
					if(c.getValue().indexOf("|" + boardNo + "|") == -1) {
						// 현재 게시글 번호가 쿠키에 없다면
						
						// 기존 값에 게시글 번호 추가해서 다시 세팅
						c.setValue( c.getValue() + "|" + boardNo + "|" );
						
						// 조회수 증가 서비스 호출
						result = service.updateReadCount(boardNo);
						
					}
				} // 3) 종료
				
				// 쿠키의 수명 세팅
				if(result > 0) {
					board.setReadCount(board.getReadCount() + 1);
					// 조회된 board 조회 수와 DB 조회 수 동기화
					
					// 적용 경로 설정
					c.setPath("/"); //  "/" 이하 경로 요청 시 쿠키 서버로 전달
					
					// 수명 지정
					Calendar cal = Calendar.getInstance(); // 싱글톤 패턴(미리 만들어진 하나의 객체만을 이용)
					cal.add(cal.DATE, 1);
					
					// 날짜 표기법 변경 객체 (DB의 TO_CHAR()와 비슷)
					SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
					
					// java.util.Date
					Date a = new Date(); // 현재 시간
					
					Date temp = new Date(cal.getTimeInMillis()); // 내일 (24시간 후)
					// 2023-05-11 12:16:10
					
					Date b = sdf.parse(sdf.format(temp)); // 내일 0시 0분 0초
					
					
					// 내일 0시 0분 0초 - 현재 시간
					long diff = (b.getTime()  -  a.getTime()) / 1000; 
					// -> 내일 0시 0분 0초까지 남은 시간을 초단위로 반환
					
					c.setMaxAge((int)diff); // 수명 설정
					
					resp.addCookie(c); // 응답 객체를 이용해서
									   // 클라이언트에게 전달
				}
				
				
			}
			
			
			// ---------------------------------------------------------
			
			path = "board/boardDetail"; // forward 할 jsp 경로
			model.addAttribute("board", board);
			
		} else { // 조회 결과가 없을 경우
			path = "redirect:/board/" + boardCode;
			// 게시판 첫페이지로 리다이렉트
			
			ra.addFlashAttribute("message", "해당 게시글이 존재하지 않습니다");
			
		}
		
		return path;
		
	}
	
	// 좋아요 처리
	@PostMapping("/like")
	@ResponseBody // 반환되는 값이 비동기 요청한 곳으로 돌아가게 함
	public int like(@RequestBody Map<String, Integer> paramMap) {
		return service.like(paramMap);
	}
	
	
	
	

}

BoardService.java

package edu.kh.project.board.model.service;

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

import edu.kh.project.board.model.dto.Board;

public interface BoardService {

	List<Map<String, Object>> selectBoardTypeList();

	/** 게시글 목록 조회
	 * @param boardCode
	 * @param cp
	 * @return map
	 */
	Map<String, Object> selectBoardList(int boardCode, int cp);

	/** 게시글 상세 조회
	 * @param map
	 * @return board
	 */
	Board selectBoard(Map<String, Object> map);

	/** 좋아요 여부 확인 서비스
	 * @param map
	 * @return result
	 */
	int boardLikeCheck(Map<String, Object> map);

	/** 조회수 증가 서비스
	 * @param boardNo
	 * @return result
	 */
	int updateReadCount(int boardNo);

	int like(Map<String, Integer> paramMap);

	/** 게시글 목록 조회 (검색)
	 * @param paramMap
	 * @param cp
	 * @return boardList
	 */
	Map<String, Object> selectBoardList(Map<String, Object> paramMap, int cp);

}

BoardServiceImpl.java

package edu.kh.project.board.model.service;

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import edu.kh.project.board.model.dao.BoardDAO;
import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.dto.Pagination;

@Service
public class BoardServiceImpl implements BoardService{
	
	@Autowired
	private BoardDAO dao;

	// 게시판 종류 목록 조회
	@Override
	public List<Map<String, Object>> selectBoardTypeList() {
		return dao.selectBoardTypeList();
	}

	// 게시글 목록 조회
	@Override
	public Map<String, Object> selectBoardList(int boardCode, int cp) {
		
		// 1. 특정 게시판의 삭제되지 않은 게시글 수 조회
		int listCount = dao.getListCount(boardCode);
		
		// 2. 1번 조회 결과 + cp 를 이용해서 Pagination 객체 생성
		// -> 내부 필드가 모두 계산되어 초기화됨
		Pagination pagination = new Pagination(listCount, cp);
		
		// 3. 특정 게시판에서
		// 현재 페이지에 해당하는 부분에 대한 게시글 목록 조회
		// ex) 100개
		//	   10개 씩 보여준다
		//	   1page -> 100 ~ 91
		//     2page -> 90  ~ 81
		// 어떤 게시판에서(boardCode)
		// 몇페이지(pagination.currentPage)에 대한
		// 게시글 몇개(pagination.limit)인지 조회
		List<Board> boardList = dao.selectBoardList(pagination, boardCode);
		
		// 4. pagination, boardList를 Map에 담아서 반환
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("pagination", pagination);
		map.put("boardList", boardList);
		
		
		return map;
	}

	// 게시글 상세 조회
	@Override
	public Board selectBoard(Map<String, Object> map) {
		return dao.selectBoard(map);
	}

	// 좋아요 여부 확인 서비스
	@Override
	public int boardLikeCheck(Map<String, Object> map) {
		return dao.boardLikeCheck(map);
	}

	// 조회수 증가 서비스
	@Transactional(rollbackFor = Exception.class)
	@Override
	public int updateReadCount(int boardNo) {
		return dao.updateReadCount(boardNo);
	}

	// 좋아요 처리 서비스
	@Transactional(rollbackFor = Exception.class)
	@Override                            // paramMap => boardNo, loginMemberNo, check
	public int like(Map<String, Integer> paramMap) {
		
		// check == 0 / 1
		// check 값이 무엇이냐에 따라서 BOARD_LIKE 테이블 INSERT / DELETE
		
		// 결과 저장용 변수선언
		int result = 0;
		
		if(paramMap.get("check") == 0) { // 좋아요 상태 X
			// BOARD_LIKE 테이블 INSERT ( dao.insertBoardLike() )
			result = dao.insertBoardLike(paramMap);
			
		} else { // 좋아요 상태 O
			// BOARD_LIKE 테이블 DELETE ( dao.deleteBoardLike() )
			result = dao.deleteBoardLike(paramMap);
			
		}
		
		if(result == 0) return -1;
		
		// 현재 게시글의 좋아요 개수 조회
		int count = dao.countBoardLike(paramMap.get("boardNo"));
		
		return count;
	}

	// 게시글 목록 조회(검색)
	@Override
	public Map<String, Object> selectBoardList(Map<String, Object> paramMap, int cp) {
		
		// 1. 특정 게시판의 삭제되지 않았고, 검색 조건이 일치하는 게시글 수 조회
		int listCount = dao.getListCount(paramMap); // 오버로딩
		
		// 2. 1번 조회 결과 + cp 를 이용해서 Pagination 객체 생성
		// -> 내부 필드가 모두 계산되어 초기화됨
		Pagination pagination = new Pagination(listCount, cp);
		
		// 3. 특정 게시판에서
		// 현재 페이지에 해당하는 부분에 대한 게시글 목록 조회
		// 단 , 검색 조건 일치하는 글만
		List<Board> boardList = dao.selectBoardList(pagination, paramMap);
		
		// 4. pagination, boardList를 Map에 담아서 반환
		Map<String, Object> map = new HashMap<String, Object>();
		map.put("pagination", pagination);
		map.put("boardList", boardList);
		
		
		return map;
	}
	
}

BoardDAO.java

package edu.kh.project.board.model.dao;

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

import org.apache.ibatis.session.RowBounds;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import edu.kh.project.board.model.dto.Board;
import edu.kh.project.board.model.dto.Pagination;

@Repository
public class BoardDAO {
	
	@Autowired
	private SqlSessionTemplate sqlSession;
	
	
	/** 게시판 종류 목록 조회
	 * @return
	 */
	public List<Map<String, Object>> selectBoardTypeList() {
		return sqlSession.selectList("boardMapper.selectBoardTypeList");
	}


	/** 특정 게시판의 삭제되지 않은 게시글 수 조회
	 * @param boardCode
	 * @return listCount
	 */
	public int getListCount(int boardCode) {
		return sqlSession.selectOne("boardMapper.getListCount", boardCode);
	}


	/** 특정 게시판에서 현재 페이지에 해당하는 부분에 대한 게시글 목록 조회
	 * @param pagination
	 * @param boardCode
	 * @return
	 */
	public List<Board> selectBoardList(Pagination pagination, int boardCode) {
		
		// RowBounds 객체
		// - 마이바티스에서 페이징처리를 위해 제공하는 객체
		// - offset 만큼 건너뛰고
		// 그 다음 지정된 행 개수만큼(limit) 만큼 조회
		
		// 1) offset 계산
		int offset
			= (pagination.getCurrentPage() - 1) * pagination.getLimit();
		
		// 2) RowBounds 객체 생성
		RowBounds rowBounds = new RowBounds(offset, pagination.getLimit());
		
		// 3) selectList("namespace.id", 파라미터(boardCode), RowBounds) 호출 
		return sqlSession.selectList("boardMapper.selectBoardList", boardCode, rowBounds);
	}


	/** 게시글 상세 조회
	 * @param map
	 * @return board
	 */
	public Board selectBoard(Map<String, Object> map) {
		return sqlSession.selectOne("boardMapper.selectBoard", map);
	}


	/** 좋아요 여부 확인 DAO
	 * @param map
	 * @return result
	 */
	public int boardLikeCheck(Map<String, Object> map) {
		return sqlSession.selectOne("boardMapper.boardLikeCheck", map);
	}


	/** 조회수 증가 DAO
	 * @param boardNo
	 * @return result
	 */
	public int updateReadCount(int boardNo) {
		return sqlSession.update("boardMapper.updateReadCount", boardNo);
	}


	/** 좋아요 테이블 삽입
	 * @param paramMap
	 * @return
	 */
	public int insertBoardLike(Map<String, Integer> paramMap) {
		return sqlSession.insert("boardMapper.insertBoardLike", paramMap);
	}


	/** 좋아요 삭제
	 * @param paramMap
	 * @return
	 */
	public int deleteBoardLike(Map<String, Integer> paramMap) {
		return sqlSession.delete("boardMapper.deleteBoardLike", paramMap);
	}


	/** 좋아요 개수 조회
	 * @param integer
	 * @return
	 */
	public int countBoardLike(Integer boardNo) {
		return sqlSession.selectOne("boardMapper.countBoardLike", boardNo);
	}


	/** 게시글 수 조회(검색)
	 * @param paramMap
	 * @return
	 */
	public int getListCount(Map<String, Object> paramMap) {
		return sqlSession.selectOne("boardMapper.getListCount_search", paramMap);
	}


	/** 게시글 목록 조회 (검색)
	 * @param pagination
	 * @param paramMap
	 * @return
	 */
	public List<Board> selectBoardList(Pagination pagination, Map<String, Object> paramMap) {
		
		// 1) offset 계산
		int offset
			= (pagination.getCurrentPage() - 1) * pagination.getLimit();
		
		// 2) RowBounds 객체 생성
		RowBounds rowBounds = new RowBounds(offset, pagination.getLimit());
		
		// 3) selectList("namespace.id", 파라미터(boardCode), RowBounds) 호출 
		return sqlSession.selectList("boardMapper.selectBoardList_search", paramMap, rowBounds);
	}

}

board-mapper.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="boardMapper">


	<!-- Board DTO에 대한 resultMap -->
	<resultMap type="Board" id="board_rm">
	
		<id property="boardNo" column="BOARD_NO"/>
		
		<result property="boardTitle" column="BOARD_TITLE"/>
		<result property="boardContent" column="BOARD_CONTENT"/>
		<result property="boardCreateDate" column="B_CREATE_DATE"/>
		<result property="boardUpdateDate" column="B_UPDATE_DATE"/>
		<result property="readCount" column="READ_COUNT"/>
		<result property="commentCount" column="COMMENT_COUNT"/>
		<result property="likeCount" column="LIKE_COUNT"/>
		<result property="memberNickname" column="MEMBER_NICKNAME"/>
		
		<result property="memberNo" column="MEMBER_NO"/>
		<result property="profileImage" column="PROFILE_IMG"/>
		
		<result property="thumbnail" column="THUMBNAIL"/>
		
		<!-- collection 태그 
			select로 조회된 결과를 컬렉션(List)에 담아
			지정된 필드에 세팅
			
			property : List를 담을 DTO의 필드명
			select : 실행할 select의 id
			column : 조회 결과 중 지정된 컬럼의 값을 파라미터로 전달
			javaType : List(컬렉션)의 타입을 지정
			ofType : List(컬렉션)의 제네릭(타입 제한) 지정
		-->
		<collection property="imageList" 
					select="selectImageList"
					column="BOARD_NO"
					javaType="java.util.ArrayList"
					ofType="BoardImage"
		/>
		
		<collection property="commentList" 
					select="selectCommentList"
					column="BOARD_NO"
					javaType="java.util.ArrayList"
					ofType="Comment"
		/>
	</resultMap>
	
	
	<!-- BoardImage resultMap -->
	<resultMap type="BoardImage" id="boardImage_rm">
		<id property="imageNo" column="IMG_NO"/>
		
		<result property="imagePath" column="IMG_PATH"/>
		<result property="imageReName" column="IMG_RENAME"/>
		<result property="imageOriginal" column="IMG_ORIGINAL"/>
		<result property="imageOrder" column="IMG_ORDER"/>
		<result property="boardNo" column="BOARD_NO"/>
	</resultMap>
	
	
	<!-- Comment resultMap -->
	<resultMap type="Comment" id="comment_rm">
		<id property="commentNo" column="COMMENT_NO"/>
		<result property="commentContent" column="COMMENT_CONTENT"/>
		<result property="commentCreateDate" column="C_CREATE_DATE"/>
		<result property="boardNo" column="BOARD_NO"/>
		<result property="memberNo" column="MEMBER_NO"/>
		<result property="commentDeleteFlag" column="COMMENT_DEL_FL"/>
		<result property="parentNo" column="PARENT_NO"/>
		<result property="memberNickname" column="MEMBER_NICKNAME"/>
		<result property="profileImage" column="PROFILE_IMG"/>
	</resultMap>
	
	

	<!-- 
		resultType이 "map"인 경우
		K : 컬럼명(BOARD_CODE, BOARD_NAME)
		V : 컬럼 값(	  1	   ,   공지 사항  )
		
		[{BOARD_NAME= 공지사항, BOARD_CODE=1}, {BOARD_NAME= 자유게시판, BOARD_CODE=2}, ...]
	 -->

	<!-- 게시판 종류 목록 조회 -->
	<select id="selectBoardTypeList" resultType="map">
		SELECT * FROM "BOARD_TYPE" ORDER BY 1
	</select>
	
	<!-- 특정 게시판의 삭제되지 않은 게시글 수 조회 -->
	<select id="getListCount" resultType="_int">
		SELECT COUNT(*) FROM BOARD
		WHERE BOARD_DEL_FL = 'N'
		AND BOARD_CODE = #{boardCode}
	</select>

	<!-- CDATA 태그 : 해당 태그 내부에 작성된 것은 모두 문자로 취급 -->
	<select id="selectBoardList" resultMap="board_rm">
		SELECT BOARD_NO, BOARD_TITLE, MEMBER_NICKNAME, READ_COUNT,
		
			<![CDATA[
				CASE
					WHEN SYSDATE - B_CREATE_DATE < 1/24/60
					THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60 * 60 ) || '초 전'
					WHEN SYSDATE - B_CREATE_DATE < 1/24
					THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60) || '분 전'
					WHEN SYSDATE - B_CREATE_DATE < 1
					THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24) || '시간 전'
					ELSE TO_CHAR(B_CREATE_DATE, 'YYYY-MM-DD')
				END B_CREATE_DATE,
			]]>
			(SELECT COUNT(*) FROM "COMMENT" C
			 WHERE C.BOARD_NO = B.BOARD_NO) COMMENT_COUNT,
			(SELECT COUNT(*) FROM BOARD_LIKE L
			 WHERE L.BOARD_NO = B.BOARD_NO) LIKE_COUNT,
			(SELECT IMG_PATH || IMG_RENAME FROM BOARD_IMG I
			WHERE I.BOARD_NO = B.BOARD_NO
			AND IMG_ORDER = 0) THUMBNAIL
		FROM "BOARD" B
		JOIN "MEMBER" USING(MEMBER_NO)
		WHERE BOARD_DEL_FL = 'N'
		AND BOARD_CODE = #{boardCode}
		ORDER BY BOARD_NO DESC
	</select>
	
	
	<select id="selectBoard" resultMap="board_rm">
		SELECT BOARD_NO, BOARD_TITLE, BOARD_CONTENT, BOARD_CODE,
			READ_COUNT, MEMBER_NICKNAME, MEMBER_NO, PROFILE_IMG,
			TO_CHAR(B_CREATE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') B_CREATE_DATE,
			TO_CHAR(B_UPDATE_DATE, 'YYYY"년" MM"월" DD"일" HH24:MI:SS') B_UPDATE_DATE,
			(SELECT COUNT(*)
			FROM "BOARD_LIKE" L
			WHERE L.BOARD_NO = B.BOARD_NO) LIKE_COUNT
		FROM "BOARD" B
		JOIN "MEMBER" USING(MEMBER_NO)
		WHERE BOARD_DEL_FL = 'N'
		AND BOARD_CODE = #{boardCode}
		AND BOARD_NO = #{boardNo}
	</select>
	
	<!-- 특정 게시글 이미지 조회 -->
	<select id="selectImageList" resultMap="boardImage_rm">
		SELECT * FROM BOARD_IMG
		WHERE BOARD_NO = #{boardNo}
		ORDER BY IMG_ORDER
	</select>
	
	<!-- 특정 게시글 댓글 조회(바뀔 예정) -->
	<select id="selectCommentList" resultMap="comment_rm">
		SELECT LEVEL, C.* FROM
			(SELECT COMMENT_NO, COMMENT_CONTENT,
			TO_CHAR(C_CREATE_DATE, 'YYYY"년" MM"월" DD"일" HH24"시" MI"분" SS"초"') C_CREATE_DATE,
			BOARD_NO, MEMBER_NO, MEMBER_NICKNAME, PROFILE_IMG, PARENT_NO, COMMENT_DEL_FL
			FROM "COMMENT"
			JOIN MEMBER USING(MEMBER_NO)
			WHERE BOARD_NO = #{boardNo}) C
		WHERE COMMENT_DEL_FL = 'N'
		START WITH PARENT_NO IS NULL
		CONNECT BY PRIOR COMMENT_NO = PARENT_NO
		ORDER SIBLINGS BY COMMENT_NO
	</select>
	
	
	<!-- 좋아요 여부 확인 -->
	<select id="boardLikeCheck" resultType="_int">
		SELECT COUNT(*) FROM BOARD_LIKE
		WHERE BOARD_NO = #{boardNo}
		AND MEMBER_NO = #{memberNo}
	</select>
	
	<!-- 조회 수 증가 -->
	<update id="updateReadCount">
		UPDATE "BOARD" SET
		READ_COUNT = READ_COUNT + 1
		WHERE BOARD_NO = #{boardNo}
	</update>
	
	<!-- 좋아요 삽입 -->
	<insert id="insertBoardLike">
		INSERT INTO "BOARD_LIKE" VALUES (#{boardNo}, #{memberNo})
	</insert>
	
	<!-- 좋아요 삭제 -->
	<delete id="deleteBoardLike">
		DELETE FROM "BOARD_LIKE"
		WHERE BOARD_NO = #{boardNo}
		AND MEMBER_NO = #{memberNo}
	</delete>
	
	<!-- 좋아요 조회 -->
	<select id="countBoardLike" resultType="_int">
		SELECT COUNT(*) FROM "BOARD_LIKE" WHERE BOARD_NO = #{boardNo}
	</select>
	
	
	<!-- 게시글 삽입 -->
	<!-- 
		동적 SQL
		- 프로그램 수행 중 SQL을 변경하는 기능
		
		<selectKey> 태그 : INSERT/UPDATE 시 사용할 키(시퀀스)를
					조회해서 파라미터의 지정된 필드 대입
	
		useGeneratedKeys : DB 내부적으로 생성한키 (시퀀스) 를
					전달된 파라미터의 필드로 대입 가능 여부 지정
					
		order="BEFORE" : 메인 SQL이 수행되기 전에 selectKey가 수행되도록 지정
		
		keyProperty : selectKey 조회 결과를 저장할 파라미터의 필드
					Board의 boardNo 필드에다가 조회 결과를 저장하겠다.			
	
	 -->
	
	
	<insert id="boardInsert" parameterType="Board" useGeneratedKeys="true">
	
		<selectKey order="BEFORE" resultType="_int" keyProperty="boardNo">
			SELECT SEQ_BOARD_NO.NEXTVAL FROM DUAL
		</selectKey>
		
			INSERT INTO BOARD
			VALUES( #{boardNo},
					#{boardTitle},
					#{boardContent},
					DEFAULT, DEFAULT, DEFAULT, DEFAULT,
					#{memberNo},
					#{boardCode} )
	</insert>
	
	
	<!-- 
		동적 SQL 중 <foreach>
		- 특정 sql 구문을 반복할 때 사용
		- 반복되는 사이에 구분자를 추가할 수 있음.
	
	 -->
	 
	 <!-- 이미지 리스트(여러개)삽입 -->
	 <insert id="insertImageList" parameterType="list">
	 	INSERT INTO "BOARD_IMG"
	 	SELECT SEQ_IMG_NO.NEXTVAL, A.*
	 	FROM (
	 		
	 		<foreach collection="list" item="img" separator=" UNION ALL ">
	 			SELECT #{img.imagePath} IMG_PATH,
	 				#{img.imageReName} IMG_RENAME,
	 				#{img.imageOriginal} IMG_ORIGINAL,
	 				#{img.imageOrder} IMG_ORDER,
	 				#{img.boardNo} BOARD_NO
	 			FROM DUAL
	 		</foreach>
	 	) A
	 	
	 </insert>
	 
	 
	 <!-- 게시글 수정 -->
	 <update id="boardUpdate">
	 	UPDATE "BOARD" SET
	 	BOARD_TITLE = #{boardTitle},
	 	BOARD_CONTENT = #{boardContent},
	 	B_UPDATE_DATE = SYSDATE
	 	WHERE BOARD_CODE = #{boardCode}
	 	AND BOARD_NO = #{boardNo}
	 </update>
	 
	 
	 <!-- 이미지 삭제 -->
	 <delete id="imageDelete">
	 	DELETE FROM "BOARD_IMG"
	 	WHERE BOARD_NO = #{boardNo}
	 	AND IMG_ORDER IN ( ${deleteList} )
	 </delete>
	 
	 
	<!-- 이미지 수정 -->
	<update id="imageUpdate">
		UPDATE "BOARD_IMG" SET
		IMG_PATH = #{imagePath},
		IMG_ORIGINAL = #{imageOriginal},
		IMG_RENAME = #{imageReName}
		WHERE BOARD_NO = #{boardNo}
		AND IMG_ORDER = #{imageOrder}
	</update>
	
	<!-- 이미지 삽입 -->
	<insert id="imageInsert">
		INSERT INTO "BOARD_IMG"
		VALUES(SEQ_IMG_NO.NEXTVAL, #{imagePath}, #{imageReName},
			#{imageOriginal}, #{imageOrder}, #{boardNo}
		)
	</insert>
	
	<!-- 게시글 삭제 -->
	<update id="boardDelete">
		UPDATE BOARD SET
		BOARD_DEL_FL = 'Y'
		WHERE BOARD_CODE = #{boardCode}
		AND BOARD_NO = #{boardNo}
	</update>
	
	
	
	<!-- 특정 게시판의 삭제되지 않고, 검색 조건이 일치하는 게시글 수 조회 -->
	<select id="getListCount_search" resultType="_int">
		SELECT COUNT(*) FROM BOARD
		
		<!-- 작성자 검색 -->
		<if test='key == "w"'>
			JOIN "MEMBER" USING(MEMBER_NO)
		</if>
		
		WHERE BOARD_DEL_FL = 'N'
		AND BOARD_CODE = #{boardCode}
		
		<choose>
			<when test='key == "t"'>
				<!-- 제목 -->
				AND BOARD_TITLE LIKE '%${query}%'
			</when>
			
			<when test='key == "c"'>
				<!-- 내용 -->
				AND BOARD_CONTENT LIKE '%${query}%'
			</when>
			
			<when test='key == "tc"'>
				<!-- 제목 + 내용 -->
				AND (BOARD_TITLE LIKE '%${query}%' OR BOARD_CONTENT LIKE '%${query}%')
			</when>
			
			<when test='key == "w"'>
				<!-- 작성자(닉네임) -->
				AND MEMBER_NICKNAME LIKE '%${query}%'
			</when>
			
		</choose>
		
	</select>
	
	
	<!-- CDATA 태그 : 해당 태그 내부에 작성된 것은 모두 문자로 취급 -->
	<!-- 게시글 목록 조회(검색) -->
	<select id="selectBoardList_search" resultMap="board_rm">
		SELECT BOARD_NO, BOARD_TITLE, MEMBER_NICKNAME, READ_COUNT,
		
			<![CDATA[
				CASE
					WHEN SYSDATE - B_CREATE_DATE < 1/24/60
					THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60 * 60 ) || '초 전'
					WHEN SYSDATE - B_CREATE_DATE < 1/24
					THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24 * 60) || '분 전'
					WHEN SYSDATE - B_CREATE_DATE < 1
					THEN FLOOR( (SYSDATE - B_CREATE_DATE) * 24) || '시간 전'
					ELSE TO_CHAR(B_CREATE_DATE, 'YYYY-MM-DD')
				END B_CREATE_DATE,
			]]>
			(SELECT COUNT(*) FROM "COMMENT" C
			 WHERE C.BOARD_NO = B.BOARD_NO) COMMENT_COUNT,
			(SELECT COUNT(*) FROM BOARD_LIKE L
			 WHERE L.BOARD_NO = B.BOARD_NO) LIKE_COUNT,
			(SELECT IMG_PATH || IMG_RENAME FROM BOARD_IMG I
			WHERE I.BOARD_NO = B.BOARD_NO
			AND IMG_ORDER = 0) THUMBNAIL
		FROM "BOARD" B
		JOIN "MEMBER" USING(MEMBER_NO)
		WHERE BOARD_DEL_FL = 'N'
		AND BOARD_CODE = #{boardCode}
		
		<choose>
			<when test='key == "t"'>
				<!-- 제목 -->
				AND BOARD_TITLE LIKE '%${query}%'
			</when>
			
			<when test='key == "c"'>
				<!-- 내용 -->
				AND BOARD_CONTENT LIKE '%${query}%'
			</when>
			
			<when test='key == "tc"'>
				<!-- 제목 + 내용 -->
				AND (BOARD_TITLE LIKE '%${query}%' OR BOARD_CONTENT LIKE '%${query}%')
			</when>
			
			<when test='key == "w"'>
				<!-- 작성자(닉네임) -->
				AND MEMBER_NICKNAME LIKE '%${query}%'
			</when>
			
		</choose>
		
		
		ORDER BY BOARD_NO DESC
	</select>
	 

</mapper>

검색창에 제목 옵션 클릭 후, 안녕 입력 시 해당 화면으로 전환됨.


0개의 댓글