Burninghering's Blog
article thumbnail

1.검색할 대상을 뭐로 선택하느냐에 따라 쿼리가 달라지므로 동적쿼리

2.페이지 이동 처리

sql을 공통부분으로 만들고, include로 공통부분을 받아온다.

A = 내용 + 제목

T = 제목

W = 작성자

if문으로 하기엔 셋 다 조건에 맞을 수 있으므로

우리가 지금 개발하려는 기능에는 choose가 낫다

%를 와일드카드라고 하는데, 여러 글자를 뜻함(0+, 0이거나 더 많거나 - title도 되고 title1도 된다.)

_는 한글자를 뜻함 (title_일때, title은 안되고 title1은 된다.)

bno가 여러개일 때, foreach로 배열을 만들어서 구분자를 콤마로 해서 값들을 다 구성해주는 것

(where bno in (1,2,3) 이라는 sql절도 있지만 훨씬 더 많을 때 사용)



 boardMapper.xml

<select id="selectPage" parameterType="SearchCondition" resultType="BoardDto">
    SELECT bno, title, content, writer, view_cnt, comment_cnt, reg_date
    FROM board
    WHERE true
    AND title LIKE concat('%',#{keyword},'%')
    ORDER BY reg_date DESC,bno DESC
        LIMIT #{offset},#{pageSize}
</select>

 

SearchCondition이라는 parameterType앞에 패키지명을 제외하려면,

mybatis-config.xml로 가서 수정해주어야 한다

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="BoardDto" type="com.fastcampus.ch4.domain.BoardDto"/>
        <typeAlias alias="SearchCondition" type="com.fastcampus.ch4.domain.SearchCondition"/>
    </typeAliases>
</configuration>

 

 boardMapper.xml

    <select id="searchResultCnt" parameterType="SearchCondition" resultType="int">
        SELECT count(*)
        FROM board
        WHERE true
        AND title LIKE concat('%', #{keyword}, '%')

    </select>

 

기본적인 쿼리로 전부 다 작성 완료


BoardDaoImpl.java

Mapper에 SQL을 만들어놓았으니, 지금 만든 SQL을 호출하는 메소드들을 만든다.

    @Override
    public int searchResultCnt(SearchCondition sc) throws Exception {
        return session.selectOne(namespace+"searchResultCnt",sc);
    }

    @Override
    public List<BoardDto> searchSelectPage(SearchCondition sc) throws Exception {
        return session.selectList(namespace+"searchSelectPage", sc);
    }

 

위에 만든 두 메소드들을 복사해서

BoardDao.interface에 추가해준다.

int searchResultCnt(SearchCondition sc) throws Exception;
List<BoardDto> searchSelectPage(SearchCondition sc) throws Exception;

 

domain에

SearchCondition.java를 만든다.

검색 조건이므로 

제공해야하는 것이 키워드,오프셋,페이지사이즈,옵션바다.

기본값을 주는 이유는 컨트롤러에서 값을 안 줬을때 기본값을 사용하기 위해서다.

package com.fastcampus.ch4.domain;

public class SearchCondition {
    private Integer page=1;
    private Integer pageSize=10;
    private Integer offset=0;
    private String keyword="";
    private String option="";

    public SearchCondition(){};
    public SearchCondition(Integer page, Integer pageSize, String keyword, String option) {
        this.page = page;
        this.pageSize = pageSize;
        this.keyword = keyword;
        this.option = option;
    }

    public Integer getPage() {
        return page;
    }

    public void setPage(Integer page) {
        this.page = page;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public Integer getOffset() {
        return offset;
    }

    public void setOffset(Integer offset) {
        this.offset = offset;
    }

    public String getKeyword() {
        return keyword;
    }

    public void setKeyword(String keyword) {
        this.keyword = keyword;
    }

    public String getOption() {
        return option;
    }

    public void setOption(String option) {
        this.option = option;
    }
}

 

새로 SQL을 만들어줬으니 테스트를 한다.

BoardDaoImplTest.java

    @Test
    public void searchSelectPageTest() throws Exception{
        boardDao.deleteAll();
        for (int i=1; i<=20; i++){
            BoardDto boardDto = new BoardDto("title"+i,"asdfasdf","asdf");
            boardDao.insert(boardDto);
        }

        SearchCondition sc = new SearchCondition(1,10,"title2","T");
        List<BoardDto> list = boardDao.searchSelectPage(sc);
//        System.out.println("list = " + list);
        assertTrue(list.size()==2); //1~20중에 title2, title20이 나와야함
    }

pom.xml에 붙여넣기

실제 실행되고 있는 sql을 보여주기 위해 다운받았다.

(아래는 설정 파일들)

 

log4jdbc.log4j2.properties

log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>

    <!-- log4jdbc-log4j2 -->
    <logger name="jdbc.sqlonly" level="INFO"/>
    <logger name="jdbc.sqltiming" level="INFO"/>
    <logger name="jdbc.audit" level="WARN"/>
    <logger name="jdbc.resultset" level="INFO"/>
    <logger name="jdbc.resultsettable" level="INFO"/>
    <logger name="jdbc.connection" level="INFO"/>
</configuration>

root-context.xml

	<!-- Root Context: defines shared resources visible to all other web components -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"></property>
		<property name="url" value="jdbc:log4jdbc:mysql://localhost:3306/springbasic?useUnicode=true&amp;characterEncoding=utf8"></property>
<!--		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>-->
<!--		<property name="url" value="jdbc:mysql://localhost:3306/springbasic?useUnicode=true&amp;characterEncoding=utf8"></property>-->
		<property name="username" value="hyerin"></property>
		<property name="password" value="hyerin"></property>
	</bean>

Service 만들기

BoardServiceImpl.java

@Override
public List<BoardDto> getSearchResultPage(SearchCondition sc) throws Exception{
    return boardDao.searchSelectPage(sc);
}

@Override
public int getSearchResultCnt(SearchCondition sc) throws Exception{
    return boardDao.searchResultCnt(sc);
}

 

BoardService.java

   List<BoardDto> getSearchResultPage(SearchCondition sc) throws Exception;

   int getSearchResultCnt(SearchCondition sc) throws Exception;

 

Service Test는 생략됨 ㅜ.ㅜ


Controller를 고친다

BoardController.java

@GetMapping("/list")
public String list(Integer page, Integer pageSize, Model m, HttpServletRequest request) {
    if(!loginCheck(request))
        return "redirect:/login/login?toURL="+request.getRequestURL();  // 로그인을 안했으면 로그인 화면으로 이동

    if(page==null) page=1;
    if(pageSize==null) pageSize=10;

    try {

        int totalCnt = boardService.getCount();
        PageHandler pageHandler = new PageHandler(totalCnt,page,pageSize); //페이지 네비게이션 1

        Map map = new HashMap();
        map.put("offset", (page-1)*pageSize);
        map.put("pageSize", pageSize);

        List<BoardDto> list = boardService.getPage(map);
        m.addAttribute("list", list);
        m.addAttribute("ph",pageHandler); //페이지 네비게이션 2

        m.addAttribute("page",page); //페이지 읽기 - 아래줄까지 추가
        m.addAttribute("pageSize",pageSize);

    } catch (Exception e) {
        throw new RuntimeException(e);
    }

    return "boardList"; // 로그인을 한 상태이면, 게시판 화면으로 이동
}

아래와 같이 소스 고침

@GetMapping("/list")
public String list(@ModelAttribute SearchCondition sc, Model m, HttpServletRequest request) {
    if(!loginCheck(request))
        return "redirect:/login/login?toURL="+request.getRequestURL();


    try {
        int totalCnt = boardService.getSearchResultCnt(sc);
        m.addAttribute("totalCnt",totalCnt);

        PageHandler pageHandler = new PageHandler(totalCnt,sc);

        List<BoardDto> list = boardService.getSearchResultPage(sc);
        m.addAttribute("list", list);
        m.addAttribute("ph",pageHandler);


    } catch (Exception e) {
        throw new RuntimeException(e);
    }

    return "boardList"; // 로그인을 한 상태이면, 게시판 화면으로 이동
}

 

PageHandler 우클릭 > Go to > Implementation

PageHandler.java

public class PageHandler {
//    private int page; //현재 페이지
//    private int pageSize; //한 페이지의 크기
//    private String option;
//    private String keyword;
    private SearchCondition sc;
    

원래 페이지와 페이지 사이즈만 받았는데, 옵션과 키워드도 받았다.

4개를 한 개의 변수로 묶어준다 (sc로)

 

page랑 pageSize가 들어간 것들 다 없애버리기

package com.fastcampus.ch4.domain;

public class PageHandler {
//    private int page; //현재 페이지
//    private int pageSize; //한 페이지의 크기
//    private String option;
//    private String keyword;
    private SearchCondition sc;

    private int totalCnt; //총 게시물 개수
    private int naviSize=10; //페이지 네비게이션의 크기
    private int totalPage; //전체 페이지의 갯수
    private int beginPage; //네비게이션의 첫번째 페이지
    private int endPage; // 네비게이션의 마지막 페이지
    private boolean showPrev; //이전 페이지로 이동하는 링크를 보여줄 것인지의 여부
    private boolean showNext; //다음 페이지로 이동하는 링크를 보여줄 것인지의 여부

    public PageHandler(int totalCnt, SearchCondition sc){
        this.totalCnt=totalCnt;
        this.sc=sc;

        doPaging(totalCnt,sc);
    }

    public void doPaging(int totalCnt,SearchCondition sc){ //페이징 계산하는데 필요한 변수3개로 생성자 만들기
        this.totalCnt=totalCnt;

        totalPage = (int)Math.ceil(totalCnt/(double)sc.getPageSize()); //Math.ceil=올림, double로 나오므로 int화
        beginPage = (sc.getPage()-1)/naviSize*naviSize+1; //일의 자릿수를 버리기
        endPage = Math.min(beginPage + naviSize -1 ,totalPage);
        showPrev=beginPage!=1; //beginPage가 1만 아니면 된다.(1만 아니면 이전으로 가는 버튼을 보여준다)
        showNext=endPage!=totalPage;
    }

    public int getTotalCnt() {
        return totalCnt;
    }

    public void setTotalCnt(int totalCnt) {
        this.totalCnt = totalCnt;
    }


    public int getNaviSize() {
        return naviSize;
    }

    public void setNaviSize(int naviSize) {
        this.naviSize = naviSize;
    }

    public int getTotalPage() {
        return totalPage;
    }

    public void setTotalPage(int totalPage) {
        this.totalPage = totalPage;
    }


    public int getBeginPage() {
        return beginPage;
    }

    public void setBeginPage(int beginPage) {
        this.beginPage = beginPage;
    }

    public int getEndPage() {
        return endPage;
    }

    public void setEndPage(int endPage) {
        this.endPage = endPage;
    }

    public boolean isShowPrev() {
        return showPrev;
    }

    public void setShowPrev(boolean showPrev) {
        this.showPrev = showPrev;
    }

    public boolean isShowNext() {
        return showNext;
    }

    public void setShowNext(boolean showNext) {
        this.showNext = showNext;
    }

    void print(){
        System.out.println("page = "+sc.getPage());
        System.out.print(showPrev ? "[PREV]" : "");
        for (int i = beginPage; i <= endPage; i++) {
            System.out.print(i+" ");
        }
        System.out.println(showNext? "[NEXT]" : "");
    }

    @Override
    public String toString() {
        return "PageHandler{" +
                "sc=" + sc +
                ", totalCnt=" + totalCnt +
                ", naviSize=" + naviSize +
                ", totalPage=" + totalPage +
                ", beginPage=" + beginPage +
                ", endPage=" + endPage +
                ", showPrev=" + showPrev +
                ", showNext=" + showNext +
                '}';
    }
}

 

SearchCondition.java에도 toString을 만들어주었다.


board.jsp와 boardList.jsp를 바꿔준다

 

그리고 SearchCondition.java에 코드를 추가해준다

    public String getQueryString(Integer page){ //page를 받아서 해당 페이지에 대한 네비게이션을 한다.
        return UriComponentsBuilder.newInstance()
                .queryParam("page",page) //지정된 페이지로 세팅이 되도록
                .queryParam("pageSize",pageSize)
                .queryParam("option",option)
                .queryParam("keyword",keyword)
                .build().toString();
    }

    public String getQueryString(){
//        //컨트롤러에서 매개변수를 받는것을 묶은 것이 SearchCondition인데, 내용을 받다가 목록으로 돌아갈 때 값을 유지해야 한다.
//        //?page=1&pageSize=10&option=T&keyword="title"
//        //위와 같이 queryString으로 줘야하는데 일일이 주기 번거로우므로 메소드를 만들었다.
//        return UriComponentsBuilder.newInstance()
//                .queryParam("page",sc.getPage())
//                .queryParam("pageSize",sc.getPageSize())
//                .queryParam("option",sc.getOption())
//                .queryParam("keyword",sc.getKeyword())
//                .build().toString();
        return getQueryString(page);
        //메소드가 중복되므로, 위 내용들을 주석처리해주고,
        //페이지를 지정해주면 그 페이지를 쓰고, 페이지를 지정해주지않으면 갖다쓴다.
    }

 

그리고 offset 변수를 없앤다.

setOffset 함수도 없앤다.

getOffset 함수만 남긴다.

//    private Integer offset=0; //page와 pageSize로 계산되는 값이기 때문에 iv로 줄 필요가 없다

boardMapper.xml에서 제대로 쿼리를 작성해보자

    <select id="searchSelectPage" parameterType="SearchCondition" resultType="BoardDto">
        SELECT bno, title, content, writer, view_cnt, comment_cnt, reg_date
        FROM board
        WHERE true
          <choose>
              <when test='option=="T"'>
                  AND title LIKE concat('%', #{keyword}, '%')
              </when>
              <when test='option=="W"'>
                  AND writer LIKE concat('%', #{keyword}, '%')
              </when>
        <otherwise>
            AND (title LIKE concat('%', #{keyword}, '%')
            OR content LIKE concat('%', #{keyword}, '%'))
        </otherwise>
          </choose>
        ORDER BY reg_date DESC, bno DESC
            LIMIT #{offset}, #{pageSize}
    </select>

    <select id="searchResultCnt" parameterType="SearchCondition" resultType="int">
        SELECT count(*)
        FROM board
        WHERE true
        <choose>
            <when test='option=="T"'>
                AND title LIKE concat('%', #{keyword}, '%')
            </when>
            <when test='option=="W"'>
                AND writer LIKE concat('%', #{keyword}, '%')
            </when>
            <otherwise>
                AND (title LIKE concat('%', #{keyword}, '%')
                OR content LIKE concat('%', #{keyword}, '%'))
            </otherwise>
        </choose>
    </select>

 

BoardDaoImplTest.java가 잘 돌아가는지 확인한다.

    @Test
    public void searchResultCntTest() throws Exception{
        boardDao.deleteAll();
        for (int i=1; i<=20; i++){
            BoardDto boardDto = new BoardDto("title"+i,"asdfasdf","asdf"+i);
            boardDao.insert(boardDto);
        }

        SearchCondition sc = new SearchCondition(1,10,"title2","T");
        int cnt = boardDao.searchResultCnt(sc);
        assertTrue(cnt==2); //1~20중에 title2, title20이 나와야함

        sc = new SearchCondition(1,10,"asdf2","W");
        cnt = boardDao.searchResultCnt(sc);
        assertTrue(cnt==2); //1~20중에 asdf2,asdf20
    }
    
	@Test
    public void searchSelectPageTest() throws Exception{
        boardDao.deleteAll();
        for (int i=1; i<=20; i++){
            BoardDto boardDto = new BoardDto("title"+i,"asdfasdf","asdf"+i);
            boardDao.insert(boardDto);
        }

        SearchCondition sc = new SearchCondition(1,10,"title2","T");
        List<BoardDto> list = boardDao.searchSelectPage(sc);
//        System.out.println("list = " + list);
        assertTrue(list.size()==2); //1~20중에 title2, title20이 나와야함

        sc = new SearchCondition(1,10,"asdf2","W");
        list = boardDao.searchSelectPage(sc);
//        System.out.println("list = " + list);
        assertTrue(list.size()==2); //1~20중에 asdf2, asdf20이 나와야함
    }

boardMapper.xml에 쿼리문이 중복이니, 별도의 sql문으로 만들어보자.

<sql id="searchCondition">
    <choose>
        <when test='option=="T"'>
            AND title LIKE concat('%', #{keyword}, '%')
        </when>
        <when test='option=="W"'>
            AND writer LIKE concat('%', #{keyword}, '%')
        </when>
        <otherwise>
            AND (title LIKE concat('%', #{keyword}, '%')
            OR content LIKE concat('%', #{keyword}, '%'))
        </otherwise>
    </choose>
</sql>

<select id="searchSelectPage" parameterType="SearchCondition" resultType="BoardDto">
    SELECT bno, title, content, writer, view_cnt, comment_cnt, reg_date
    FROM board
    WHERE true
    <include refid="searchCondition"/>
    ORDER BY reg_date DESC, bno DESC
        LIMIT #{offset}, #{pageSize}
</select>

<select id="searchResultCnt" parameterType="SearchCondition" resultType="int">
    SELECT count(*)
    FROM board
    WHERE true
    <include refid="searchCondition"/>
</select>

 


board.jsp

버전 문제 때문에

<%@taglib prefix="c" uri="http://java.sun.com/jstl/core_rt" %>

_rt를 붙여주었다


<input name="title" type="text" value="<c:out value='${boardDto.title}'/>" placeholder="  제목을 입력해 주세요." ${mode=="new" ? "" : "readonly='readonly'"}><br>
<textarea name="content" rows="20" placeholder=" 내용을 입력해 주세요." ${mode=="new" ? "" : "readonly='readonly'"}><c:out value="${boardDto.content}"/></textarea><br>

제목이나 내용에 script를 넣지 않도록 미리 방지해주는 <c:out>태그

profile

Burninghering's Blog

@개발자 김혜린

안녕하세요! 반갑습니다.