09009

[Spring] 예제 2 - 검색 기능 구현 본문

Back-End/Spring
[Spring] 예제 2 - 검색 기능 구현
09009

제목, 내용, 작성자를 기준으로 검색하는 쿼리 작성

 -- 검색 (제목에 '안녕'이라는 글자가 들어있는 게시물 추출)
SELECT fb.* FROM fp_board fb
WHERE fb.title LIKE '%'|| '안녕' ||'%'
ORDER BY id DESC;

-- 닉네임 검색
-- fp_board에 값이 없으므로 조인이나 서브쿼리 이용
SELECT fb.* 
FROM fp_board fb JOIN fp_member fm
ON fb.member_id = fm.id
WHERE fm.nickname LIKE '%'||'브'||'%'
;
-- 닉네임 검색 (서브쿼리)
SELECT fb.* FROM fp_board fb
WHERE (SELECT fm.nickname FROM fp_member fm WHERE fm.id = fb.member_id) LIKE '%'||'브'||'%'

문자열 연산 쿼리

 

mainPage.jsp

검색창에 해당 항목을 기준으로 검색하면 페이지는 mainPage에서 mainPage로 이동한다.

- 웹 링크에 검색어가 나오고 키워드 자체가 보안이 중요한 항목이 아니기 때문에 get 방식으로 요청해도 상관없다.

- name 속성은 select 태그에 붙이도록 한다.

   <form action="./mainPage" method="get">
      <div class="row mt-3"><!-- 검색 -->
         <div class="col-2">
            <select name="searchType" class="form-select">
               <option value="title" selected>제목</option>
               <option value="content">내용</option>
               <option value="nickname">작성자</option>
            </select>            
         </div>
         <div class="col-8">
            <input name="searchWord" type="text" class="form-control">
         </div>
         <div class="col-2 d-grid">
            <button class="btn btn-primary">검색</button>
         </div>
      </div>
	  </form>

내용으로 검색

BoardController

parameter가 받아지지 않으면 null값으로 설정된다.( String이므로 null값 허용)

searchType에 따라 실행할 쿼리만 변경해주면 된다.

	@RequestMapping("mainPage")
	public String mainPage(Model model, 
			@RequestParam(value = "page", defaultValue = "1") int page,
			String serachType, 
			String searchWord
			) {
	
		List<Map<String, Object>> list =  boardService.getBoardList(page, searchType, searchWord);
		
		int boardCount = boardService.getBoardCount();
		int totalPage = (int)Math.ceil(boardCount / 10.0);
		
		// 1 2 3 4 5     6 7 8 9 10
		int startPage = ((page - 1) / 5) * 5 + 1;
		int endPage = ((page - 1) / 5+1) * 5;
		
		// endPage가 totalPage
		if (endPage > totalPage) {
			endPage = totalPage;
		}

		model.addAttribute("list", list); //request 객체에 담는다고 생각.
		model.addAttribute("totalPage", totalPage);
		model.addAttribute("currentPage", page);
		
		model.addAttribute("startPage", startPage);
		model.addAttribute("endPage", endPage);
		
		return "board/mainPage";
	}

 

 

 

✍ BoardServiceImpl

	public List<Map<String, Object>> getBoardList(int pageNum, String searchType, String searchWord) {
			
			// List는 object를 주로 담진 않는다. 일률적으로 담는다.
			List<Map<String, Object>> list = new ArrayList<>();
			
			List<BoardDto> boardDtoList =  boardSqlMapper.selectAll(pageNum, searchType,  searchWord);
			
			for(BoardDto boardDto : boardDtoList) {
				// 한 바퀴 돌때마다 map이 생성되어야 한다.	map은 키가 중복되면 안된다.
				Map<String, Object> map = new HashMap<>();
				
				int memberId = boardDto.getMember_id();
				System.out.println(memberId);
				
				MemberDto memberDto = memberSqlMapper.selectById(memberId); 
				System.out.println(memberDto);
				
				map.put("memberDto", memberDto);
				map.put("boardDto", boardDto);
				
				list.add(map);

			}
			return list;
		}

 

mapper에서 올바르지 않은 코드

public List<BoardDto> selectAll(int pageNum, String searchType, String searchWord);

 

BoardSqlmapper.xml

mybatis에 파라미터 하나를 전송할 경우에는 상관없다

mybatis에서 2개 이상의 파라미터는 받을 수 없으므로 2개 이상을 보낼때는 param annotation을 붙여주어야 한다.

1. objectDto를 선언하여 값을 세팅후 파라미터로 넘기는 방법

2. hashMap을 생성하여 키 값 세팅하여 하나의 파라미터로 넘기는 방법이 있는데

위 두 방법을 사용하는 것보다 mybatis에서 제공하는 어노테이션을 사용하도록 한다.

 

수정된 올바른 코드

	public List<BoardDto> selectAll(
			@Param("pageNum") int pageNum, 
			@Param("searchType") String searchType, 
			@Param("searchWord") String searchWord);

검색 쿼리 다시 작성

-- 기본 쿼리
SELECT fb.*
FROM fp_board 
fb INNER JOIN fp_member fm ON fm.id = fb.member_id
ORDER BY fb.id DESC
;

-- 제목에 안녕 들어간 데이터 출력
SELECT fb.*
FROM fp_board 
fb INNER JOIN fp_member fm ON fm.id = fb.member_id
WHERE 1 = 1
AND fb.title LIKE '%'||'안녕'||'%'
ORDER BY fb.id DESC
;

-- 내용에 '안녕' 들어간 데이터 출력
SELECT fb.*
FROM fp_board 
fb INNER JOIN fp_member fm ON fm.id = fb.member_id
WHERE 1 = 1
AND fb.content LIKE '%'||'안녕'||'%'
ORDER BY fb.id DESC
;

동적 쿼리 조건문

동적 쿼리 작성 원리

 

 

cdata 안에 동적 쿼리를 작성하면 컴파일 시 에러가 발생하므로 cdata의 위치를 수정해야한다.

BoardSqlmapper.xml

동적쿼리 작성

	<select id="selectAll" resultType="com.ja.finalproject.dto.BoardDto">
	
		SELECT t2.* FROM (
			SELECT t1.*, ROWNUM rnum FROM(
		     SELECT fb.*
				FROM fp_board 
				fb INNER JOIN fp_member fm ON fm.id = fb.member_id
				WHERE 1 = 1
				<if test="searchType != null and searchWord != null">
					<choose>
						<when test="searchType == 'title'">
							AND fb.title LIKE '%'|| #{searchWord} ||'%'
						</when>
						<when test="searchType == 'content'">
							AND fb.content LIKE '%'|| #{searchWord} ||'%'
						</when>
						<when test="searchType == 'nickname'">
							AND fm.nickname LIKE '%'|| #{searchWord} ||'%'
						</when>
					</choose>
				</if>
		     	ORDER BY fb.id DESC
		    ) t1
		) t2
		<![CDATA[
		WHERE t2.rnum >= ((#{pageNum}-1)*10)+1 AND t2.rnum <= #{pageNum}*10
		]]>
		
	</select>

otherwise는 위 상황에서는 필요없다. 

 

검색 창에서 해당 항목으로 검색할 때 한 페이지에 나오는 게시글의 수도 달라지기 때문에

getBoardCount 메서드도 수정해주어야 한다.

BoardSqlmapper

	// 글 개수 가져오는 것
	public int getBoardCount(@Param("searchType") String searchType,  @Param("searchWord") String searchWord);

 

	<select id="getBoardCount" resultType="int">
		SELECT COUNT(*) FROM 
		fp_board fb INNER JOIN fp_member fm
		ON fm.id = fb.member_id
		
	</select>

분기문 작성

<select id="getBoardCount" resultType="int">
		SELECT COUNT(*) FROM 
		fp_board fb INNER JOIN fp_member fm
		ON fm.id = fb.member_id
			<if test="searchType != null and searchWord != null">
					<choose>
						<when test="searchType == 'title'">
							AND fb.title LIKE '%'|| #{searchWord} ||'%'
						</when>
						<when test="searchType == 'content'">
							AND fb.content LIKE '%'|| #{searchWord} ||'%'
						</when>
						<when test="searchType == 'nickname'">
							AND fm.nickname LIKE '%'|| #{searchWord} ||'%'
						</when>
					</choose>
				</if>
	</select>

BoardServiceImpl

		public int getBoardCount(String searchType, String searchWord) {
			return boardSqlMapper.getBoardCount(searchType, searchWord);
		}

Boardcontroller

int boardCount = boardService.getBoardCount(searchType, searchWord);
	@RequestMapping("mainPage")
	public String mainPage(Model model, 
			@RequestParam(value = "page", defaultValue = "1") int page,
			String searchType, 
			String searchWord
			) {
	
		List<Map<String, Object>> list =  boardService.getBoardList(page, searchType, searchWord);
		
		int boardCount = boardService.getBoardCount(searchType, searchWord);
		int totalPage = (int)Math.ceil(boardCount / 10.0);
		
		// 1 2 3 4 5     6 7 8 9 10
		int startPage = ((page - 1) / 5) * 5 + 1;
		int endPage = ((page - 1) / 5+1) * 5;
		
		// endPage가 totalPage
		if (endPage > totalPage) {
			endPage = totalPage;
		}

		model.addAttribute("list", list); //request 객체에 담는다고 생각.
		model.addAttribute("totalPage", totalPage);
		model.addAttribute("currentPage", page);
		
		model.addAttribute("startPage", startPage);
		model.addAttribute("endPage", endPage);
		
		return "board/mainPage";
	}

페이지를 클릭하면 링크가 풀리는 문제 해결

	String searchQueryString = "";
		if (searchType != null && searchWord != null) {
			searchQueryString += "&searchType=" + searchType;
			searchQueryString += "&searchWord=" + searchWord;	
		}
		
		model.addAttribute("searchQueryString", searchQueryString);
	@RequestMapping("mainPage")
	public String mainPage(Model model, 
			@RequestParam(value = "page", defaultValue = "1") int page,
			String searchType, 
			String searchWord
			) {
	
		List<Map<String, Object>> list =  boardService.getBoardList(page, searchType, searchWord);
		
		int boardCount = boardService.getBoardCount(searchType, searchWord);
		int totalPage = (int)Math.ceil(boardCount / 10.0);
		
		// 1 2 3 4 5     6 7 8 9 10
		int startPage = ((page - 1) / 5) * 5 + 1;
		int endPage = ((page - 1) / 5+1) * 5;
		
		// endPage가 totalPage
		if (endPage > totalPage) {
			endPage = totalPage;
		}

		model.addAttribute("list", list); //request 객체에 담는다고 생각.
		model.addAttribute("totalPage", totalPage);
		model.addAttribute("currentPage", page);
		
		model.addAttribute("startPage", startPage);
		model.addAttribute("endPage", endPage);
		
		String searchQueryString = "";
		if (searchType != null && searchWord != null) {
			searchQueryString += "&searchType=" + searchType;
			searchQueryString += "&searchWord=" + searchWord;	
		}
		
		model.addAttribute("searchQueryString", searchQueryString);
		
		return "board/mainPage";
	}

mainPage.jsp

 <div class="row"><!-- 버튼 -->
         <div class="col-6 mx-auto">
            <nav aria-label="Page navigation example">
               <ul class="pagination mb-0">
                  <c:choose>
                     <c:when test="${startPage <= 1}">
                        <li class="page-item disabled"><a class="page-link" href="./mainPage?page=${startPage-1}${searchQueryString}">&lt;</a></li>
                     </c:when>
                     <c:otherwise>
                        <li class="page-item"><a class="page-link" href="./mainPage?page=${startPage-1}${searchQueryString}">&lt;</a></li>
                     </c:otherwise>
                  </c:choose>
                  <!-- 페이지 번호 -->
                  <c:forEach begin="${startPage }" end="${endPage }" var="index">
                     <c:choose>
                        <c:when test="${index == currentPage}">
                           <li class="page-item active"><a class="page-link" href="./mainPage?page=${index }${searchQueryString}">${index }</a></li>
                        </c:when>
                        <c:otherwise>
                           <li class="page-item"><a class="page-link" href="./mainPage?page=${index }${searchQueryString}">${index }</a></li>
                        </c:otherwise>
                     </c:choose>
                  </c:forEach>
                  <c:choose>
                     <c:when test="${endPage >= totalPage}">
                         <li class="page-item disabled"><a class="page-link" href="./mainPage?page=${endPage+1}${searchQueryString}">&gt;</a></li>
                     </c:when>
                     <c:otherwise>
                         <li class="page-item"><a class="page-link" href="./mainPage?page=${endPage+1}${searchQueryString}">&gt;</a></li>
                     </c:otherwise>
                  </c:choose>
               </ul>
            </nav>   
         </div>