D+48::스프링부트_게시판 검색기능/페이징처리

Am.Vinch·2022년 9월 1일
0

20220901_thu

실습내용

  • 게시판 검색기능 구현 ( 제목/ 작성자 select 박스 추가시)
  • 검색어 대소문자 구분 않고 검색기능
  • 검색 후, 검색어 초기화시키지않고 저장
  • 페이징 기능 구현

검색 기능

목록조회

  • 쿼리문의 if문안에 있는 글자는 mybatis 에서 title 그대로 작성하면 getter 호출한다.
  • 왜? BoardVO에 있는 getter를 호출하기 때문에! -> boardVO.getTitle(), boardVO.getSearchValue() 호출된다.
  • _parameter :빈값이 하나만 있을 때, 이 하나의 빈값채우기 위해서 넘어오는 데이터로 대신 사용하는 것.
  • 하지만 검색어기능이 추가됨으로써 이제는 searchKeyword값과 searchValue값 총 두개의 빈 값이 있기 때문에 parameter값을 사용하지 않는다!
  • 쿼리문 내에서 컬럼명을 불러올 때는 홀따옴표가 붙지않은채로 들어와야한다.(ex. TITLE = #{})
  • 그래서 이를 콘솔창으로 확인시,
    #{}으로 사용하면 홀따옴표가 붙어 문자로 사용되고, ${}사용하면 홀따옴표가 붙지않아 데이터값으로 사용될 수 있다!(유의!)

검색어 대소문자 구분방지: LOWER(),UPPER()

WHERE LOWER(${searchKeyword}) LIKE '%'||LOWER(#{searchValue})||'%'


  • board-mapper(가장먼저!)
<!-- 목록조회 -->
	<select id="selectBoardList" resultMap="board">
		SELECT BOARD_NUM 
		, TITLE
		, WRITER
		, CREATE_DATE
		FROM SPRING_BOARD
		<if test="searchValue != null and !searchValue.equals('')">
		WHERE LOWER(${searchKeyword}) LIKE '%'||LOWER(#{searchValue})||'%'
		</if>
		ORDER BY BOARD_NUM DESC
	</select>

상속 (BoardVo > SearchVO)

클래스 상속

  • 단순히 쿼리문에 빈값 두개라고 boardVO 만 던져주면 안된다.
  • 매퍼 쿼리문에서 빈값들은 boardvo.getSeeacrKeyword(),boardvo.getSeearchValue() 호출한다.
  • 그런데 이전 BoardVO 클래스에는 위의 검색어 변수들이 없으므로 따로 만들어줘야한다!
  • 그래서 SearchVO 생성 대신 상속기능 해야한다!!!
  • SearchVO도 마치 BoardVO 인 것처럼 사용할 수 있도록 상속한다
    public class BoardVO extends SearchVO {}
  • BoardVO(상속)
package kh.study.board.vo;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class BoardVO extends SearchVO {
	private int boardNum;
	private String title;
	private String content;
	private String writer;
	private String createDate;
}
  • SearchVO
package kh.study.board.vo;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class SearchVO {//상속 -> boardVO
	private String searchKeyword;
	private String searchValue;

}
  • boardService 매개변수 수정 ( String title -> BoardVO boardVO )
//게시글 목록조회
	List<BoardVO> selectBoardList(BoardVO boardVO);
  • boardServiceImpl 매개변수 수정( String title -> BoardVO boardVO )
//게시글 목록조회
	@Override
	public List<BoardVO> selectBoardList(BoardVO boardVO) {
		return sqlSession.selectList("boardMapper.selectBoardList",boardVO);
	}
  • BoardController
  • @RequestMapping("/list") : getMapping,postMapping 모두 받는 어노테이션을 사용한다.
  • 검색어/검색키워드 값이 null값 뜰 때와 빈값 뜰 때 차이점 구별하는 방법
    • 주소창경로를 바로 list로 간 경우는 null
    • 검색기능에 빈값으로 검색버튼 클릭시 '' 빈값
//게시글 목록 페이지로 이동
	@RequestMapping("/list")
	public String selectBoardList(Model model,BoardVO boardVO) {
		//1-2.게시글 목록 조회(한줄요약)
		model.addAttribute("boardList",boardService.selectBoardList(boardVO));
		
		return "board/board_list";//board_list.html 
	}
  • board_list.html

검색기능

  • 넘어가는 value값은 쿼리문에 들어가는 빈값들이 컬럼명으로 들어가는 것과 같다
  • 검색버튼 클릭시,searchKeyword는 둘 중 하나를 선택하는 것이기때문에 무조건 데이터값이 넘어간다 빈값이나 null값이 뜰 수가 없다
  • 단, 기본경로 주소창 입력시에는 검색버튼을 누른 것이아니기 때문에 null값이 뜬다

  • select에서만 사용가능한 타임리프 if문 사용
    th:selected="${boardVO.searchKeyword == 'TITLE'}"
    :선택한 키워드로 검색했을 때, ''값과 일치하면 selected(기본값)으로 선택한 키워드가 저장되어 목록페이지에 뜨도록 설정하겠다.
  • 검색버튼 누르면 name값 searchValue값이 넘어간다
  • 그런데 검색어가 아무것도 없는 빈값이면 빈값으로 넘어간다 null이아니다!
  • 단, 기본경로 주소창 입력시에는 검색버튼을 누른 것이아니기 때문에 null값이 뜬다
  • th:value 타임리프적용시, 검색어 검색창에 그대로 저장 커맨드객체때문에 데이터를 넘기지않아도 자동으로 데이터가 넘어간다.
  • 넘어갈때는 클래스명에서 소문자로바꿔 자동으로 넘어간다: ${boardVO.searchValue}
<!-- 검색기능 구현 -->
<form action="/board/list" method="post">
	<select name="searchKeyword">
		<option value="TITLE" th:selected="${boardVO.searchKeyword == 'TITLE'}">제목</option>
		<option value="WRITER" th:selected="${boardVO.searchKeyword == 'WRITER'}">작성자</option>
	</select>
	<input type="text" name="searchValue" th:value="${boardVO.searchValue}">
	<input type="submit" value="검색"><br>
</form>

페이징

  • 수학함수 사용 Math.ceil() : 실수로 반올림 연산기능
  • public class PageVO extends SearchVO 상속수정

  • BoardVO 상속 수정

    상속은 하나만 가능하다!
    두개이상은 자바에서 안된다.
    searchVO와 PageVO도 마치 boardVO 인 것처럼 사용할 수 있도록 상속한다
    public class BoardVO extends PageVO

@Getter
@Setter
@ToString
//상속은 하나만 가능하다! 두개이상은 자바에서 안된다!!!
public class BoardVO extends PageVO {// searchVO도 마치 boardVO 인 것처럼 사용할 수 있도록 상속한다
	//lombok 어노테이션(위에) 을 이용해 getter,setter를 편하게 만들수있다
	private int boardNum;
	private String title;
	private String content;
	private String writer;
	private String createDate;
}
  • PageVO 상속

    BoardVO > PageVO > SearchVO 순서 상속된다.
    public class PageVO extends SearchVO

package kh.study.board.vo;

public class PageVO extends SearchVO {//연달아 상속받도록!BoardVO > PageVo > SearchVO
	private int nowPage;//현재선택된페이지
	private int totalDataCnt;//전체게시글(데이터)수
	private int beginPage;//화면에 보이는 첫 페이지
	private int endPage;//화면에 보이는 마지막 페이지
	private int displayCnt;//한 화면에 보여지는 게시글 수
	private int displayPageCnt;//한 화면에 보여지는 페이지 수
	private boolean prev;//이전 버튼의 유무
	private boolean next;//다음 버튼의 유무
	
	private int startNum;//시작 row_num 행번호
	private int endNum;//마지막 row_num 행번호
	
	//생성자(객체만들어지면서 시작되는 페이지 기본설정)
	public PageVO() {
		nowPage = 1;
		displayCnt = 10;
		displayPageCnt = 10;
	}
	public void setPageInfo() {//페이지정보 setter
		//화면에 보이는 마지막 페이지 번호
		endPage = displayPageCnt * (int)Math.ceil(nowPage/(double)displayPageCnt); 
		//시작페이지
		beginPage = endPage-displayPageCnt+1;
		//전체 페이지수 
		int totalPageCnt = (int)Math.ceil(totalDataCnt / (double)displayCnt);
		//next 버튼 유무
		if(endPage < totalPageCnt) {//화면에 보이는 마지막 페이지값이 전체페이지 값(마지막페이지값)보다 작으면 next버튼보임
			next=true;
		}
		else {
			next=false;
			endPage= totalPageCnt;//뒤에 페이지가 없으면 전체페이지까지만 보이도록!
		}
		
		//prev 유무( 삼항연산자 )
		prev = beginPage ==1? false :true;
		
		startNum =  (nowPage-1) * displayCnt +1;
		endNum = nowPage * displayCnt ;
	}
	
	public void setNowPage(int nowPage) {
		this.nowPage =nowPage;
	}
	public int getNowPage() {
		return nowPage;
	}
	public void setTotalDataCnt(int totalDataCnt) {
		this.totalDataCnt = totalDataCnt;
		
	}
	public int getTotalDataCnt() {
		return totalDataCnt;
	}
	public void setNext(boolean next) {
		this.next = next;
		
	}
	public boolean getNext() {
		return next;
	}
	public void setPrev(boolean prev) {
		this.prev = prev;
	}
	public boolean getPrev() {
		return prev;
	}
	public int getBeginPage() {
		return beginPage;
	}
	public int getEndPage() {
		return endPage;
	}
	public int getStartNum() {
		return startNum;
	}
	public int getEndNum() {
		return endNum;
	}
	
}

ROWNUM

ROWNUM 은 조회된 행번호를 부여한다.

  • 문제점은 한번 조회후, 다른 값으로 조회시 순서가 바뀌어 오류발생.
  • 처음부터 모든 게시글을 조회 후, rownum 행번호를 조회하고 게시글번호 내림차순(최신순)으로 조회하는 순서로 진행해야 문제점이 발생하지 않는다!
  • 그래서 3단 서브쿼리를 사용하여 해결한다.
  • 단, 첫번째 조회쿼리문에서 ROWNUM과 달리 두번째,세번째 쿼리문에서는 별칭을 사용해 ROW_NUM을 사용해야한다!!!

  • DB 데이터베이스
-- 게시글 목록 조회
SELECT BOARD_NUM , TITLE , WRITER , CREATE_DATE 
FROM SPRING_BOARD;

-- 페이징처리
-- 한 페이지에 10개 게시글만 조회
-- ROWNUM : 조회된 행 번호
SELECT ROWNUM , BOARD_NUM , TITLE , WRITER , CREATE_DATE 
FROM SPRING_BOARD;

-- ROWNUM이용해서 1-10까지 조회
SELECT ROWNUM , BOARD_NUM , TITLE , WRITER , CREATE_DATE 
FROM SPRING_BOARD
WHERE ROWNUM >= 1 AND ROWNUM <=10;

--ROWNUM 문제점발생: 만약 다시 11-20을 조회하면 순서가 뒤죽박죽 섞여버려 오류발생
SELECT ROWNUM , BOARD_NUM , TITLE , WRITER , CREATE_DATE 
FROM SPRING_BOARD
ORDER BY TITLE;

-- 문제점 해결 서브쿼리 작성
--예시
SELECT TITLE, CONTENT
FROM SPRING_BOARD
WHERE BOARD_NUM < 10;

-- FROM 절 안에 테이블명 대신 위에 쿼리문으로 서브쿼리 작성
-- 해석: 서브쿼리로 조회된 데이터 결과값 중에서 조회하겠다
SELECT AAA
FROM 
(
    SELECT TITLE AS AAA, CONTENT
    FROM SPRING_BOARD
    WHERE BOARD_NUM < 10
)
WHERE AAA IS NOT NULL;

-- 먼저 서브쿼리에서 ROWNUM 없이 게시글번호 내림차순으로 조회한 값에서 
-- 내림차순이면 최신글부터 보일 수 있도록 조회
-- 이후 ROWNUM 붙여 조회
SELECT ROWNUM
    , BOARD_NUM
    , TITLE
    , WRITER
    , CREATE_DATE
FROM
(
    SELECT BOARD_NUM , TITLE , WRITER , CREATE_DATE 
    FROM SPRING_BOARD
    ORDER BY BOARD_NUM DESC
)
WHERE ROWNUM >=11 AND ROWNUM <= 20;
-- 하지만 여전히 문제점 그대로 발생
-- 서브쿼리가 전체조회한 후 조건보는게 아니라
-- 한줄씩 조회하고 조건문 조회하다보니 ROWNUM 이 계속 1만 생기고 결국 조회안됨
-- 그래서 순서를 바꿔서 다시 서브쿼리 작성(3단 쿼리)
SELECT ROW_NUM-- FROM절에 있는 ROW_NUM 값
    , BOARD_NUM
    , TITLE
    , WRITER
    , CREATE_DATE
FROM
(
    SELECT ROWNUM AS ROW_NUM
        , BOARD_NUM
        , TITLE
        , WRITER
        , CREATE_DATE
    FROM
    (
        SELECT BOARD_NUM , TITLE , WRITER , CREATE_DATE 
        FROM SPRING_BOARD
        ORDER BY BOARD_NUM DESC
    )--여기 쿼리문은 조건문 없어서 모든 데이터의 ROWNUM을 조회하게됨    
)
-- 미리 싹 다 조회된 ROWNUM에서 아래조건문대로 조회하겠다.
WHERE ROW_NUM >=11 AND ROW_NUM <=20; 
  • board-mapper
  • WHERE ROW_NUM &gt;= #{startNum} AND ROW_NUM &lt;= #{endNum} 에서
    • '~보다 크거나 같다' : >= 대신 > 을 사용한다.
    • '~보다 작거나 같다' : <= 대신 < 을 사용한다.
  • 목록조회쿼리실행을 위해 전체 게시글(데이터) 수 조회 쿼리문을 새로 만들어줘야한다.
<!-- boardVO 안에 빈값이 총 4개값이 되었다. 
startNum,endNum 값은 boardVO에 없고 pageVO에 있기 때문에 상속기능! -->
	<select id="selectBoardList" resultMap="board">
		SELECT ROW_NUM
		    , BOARD_NUM
		    , TITLE
		    , WRITER
		    , CREATE_DATE
		FROM 
		(
		    SELECT ROWNUM AS ROW_NUM
		        , BOARD_NUM
		        , TITLE
		        , WRITER
		        , CREATE_DATE
		    FROM
		    (
		    	SELECT BOARD_NUM 
						, TITLE
						, WRITER
						, CREATE_DATE
				FROM SPRING_BOARD
				<if test="searchValue != null and !searchValue.equals('')">
				WHERE LOWER(${searchKeyword}) LIKE '%'||LOWER(#{searchValue})||'%'
				</if>
				ORDER BY BOARD_NUM DESC
		    )
		)
		WHERE ROW_NUM &gt;= #{startNum} AND ROW_NUM &lt;= #{endNum}
	</select>
	
    <!-- 전체 데이터(게시글) 수 조회 -->
	<select id="selectBoardCnt" resultType="int">
		SELECT COUNT(BOARD_NUM)
		FROM SPRING_BOARD
	</select>
  • BoardController

페이징 처리 위해 추가된 사항

    1. mapper에서 만든 selectBoardCnt() 메소드사용해서 전체데이터(게시글) 수 먼저 값 가져오기
      int totalCnt = boardService.selectBoardCnt();
    1. 위에서 만든 데이터값 넣어주기.
      boardVO.setTotalDataCnt(totalCnt);
    1. (목록조회 전에 미리) 페이지정보 세팅
      boardVO.setPageInfo();
	//게시글 목록 페이지로 이동
	@RequestMapping("/list")//get,post 모두 받는 어노테이션
	public String selectBoardList(Model model,BoardVO boardVO) {
		
		//전체 데이커(게시글) 수를 먼저 가져오기 
        //이때문에 mapper에서 또 조회쿼리문 생성한것
		int totalCnt = boardService.selectBoardCnt();
		
		//db에서 쿼리문 메소드기능 실행한 값(totalCnt)을 totalDataCnt에 넣어주기
        boardVO.setTotalDataCnt(totalCnt);

		//페이지 정보 세팅(목록조회전)
        boardVO.setPageInfo();
		
		model.addAttribute("boardList",boardService.selectBoardList(boardVO));
		
		return "board/board_list";//어디로 이동할래?
	}

페이지 기능구현

  • 테이블 밑에 <div>태그로 작성
  • 페이징 예시
    1 2 3 ...8 9 10 next
    prev 11 12 13 14 ..19 20 next
  • 타임리프 <a>태그 사용하여 prev, next,nowPage 페이지이동 버튼기능구현
  • <th:block th:each="pageNum : ${#numbers.sequence(boardVO.beginPage, boardVO.endPage)}"> : beginPage부터 endPage까지 pageNum숫자를 하나씩 뽑겠다.
  • board_list.html
<!-- 페이징 기능 구현 -->	
<div align="center">
	<th:block th:if="${boardVO.prev}">
	<a th:text="prev" th:href="@{/board/list(nowPage=${boardVO.beginPage-1})}"></a>
	</th:block>
   <th:block th:each="pageNum : ${#numbers.sequence(boardVO.beginPage, boardVO.endPage)}">
    <a th:text="${pageNum}" th:href="@{/board/list(nowPage=${pageNum})}"></a>	
   </th:block>
   <th:block th:if="${boardVO.next}">
   <a th:text="next" th:href="@{/board/list(nowPage=${boardVO.endPage+1})}"></a>
   </th:block>
</div>

<결과>

  • http://localhost:8081/board/list
  • 각 페이지마다 10개의 게시글이 보이며, 화면에서 보이는 페이지의 수 는 10개씩 보이도록 구현(페이지정보셋팅 setPageInfo())
  • 다른 페이지 숫자 클릭시, 해당 페이지 10개씩 게시글 출력
  • 시작페이지 1이 보일 때는 pre 버튼 보이지 않기
  • 마지막 페이지 26일 때는 나머지 27~30 페이지는 보이지 않고 next 버튼도 보이지 않도록 구현
  • 첫 게시글 목록 페이지
  • 마지막 게시글 목록 페이지
profile
Dev.Vinch

0개의 댓글