Spring MyBatis (5) 게시판 검색 기능

강서진·2024년 1월 31일
0

Spring

목록 보기
16/18
post-custom-banner

2번째 필수 강의 ch 4. 7강 요약

검색기능

검색어를 입력하고 검색을 누르면

  • 제목 + 내용
  • 제목
  • 작성자
    를 기준으로 검색하고 검색어를 포함한 게시글을 찾아 반환하도록 하는 기능을 추가해보려고 한다. 검색 기능을 만들려면 검색할 대상에 따라 쿼리가 달라지기 때문에 동적 쿼리를 작성해야 하고, 다음으로 페이지 이동 처리에도 신경써야 한다. 검색 결과에서 게시물을 하나 선택해서 읽고 다시 뒤로 간다고 할 때, 다시 원래의 검색 결과가 나와야 하고, 또 원래 있던 페이지로 돌아갈 수 있어야 한다.

이 때 검색 창은 form으로 받고, 검색 영역은 select option으로 받는다. 컨트롤러는 input으로 들어오는 검색 값만 받을 것이 아니라, 옵션값도 함께 받아야 한다.

동적 쿼리 - <sql>, <include>

동적 쿼리는 <sql>과 <include> 태그를 사용한다. 공통되는 부분은 로 정의하고, <include>로 포함시켜 재사용한다.

여기 공통된 부분을 가진 두 sql 쿼리가 있다.

    <select id="select" parameterType="int" resultType="BoardDto">
        SELECT bno, title, content, writer, view_count, comment_count, reg_date, up_date
        FROM springbasic.board
        WHERE bno = #{bno}
    </select>

    <select id="selectPage" parameterType="map" resultType="BoardDto">
        SELECT bno, title, content, writer, view_count, comment_count, reg_Date, up_date
        FROM springbasic.board
        ORDER BY reg_date desc, bno desc
        LIMIT #{offset}, #{pageSize}
    </select>

공통된 부분을 <sql>로 뽑아내면,

    <sql id="selectFromBoard">
        SELECT bno, title, content, writer, view_count, comment_count, reg_Date, up_date
        FROM springbasic.board
    </sql>

<include>로 재사용할 수 있다.

    <select id="select" parameterType="int" resultType="BoardDto">
        <include refid="selectFromBoard"/>
        WHERE bno = #{bno}
    </select>

    <select id="selectPage" parameterType="map" resultType="BoardDto">
        <include refid="selectFromBoard"/>
        ORDER BY reg_date desc, bno desc
        LIMIT #{offset}, #{pageSize}
    </select>

동적 쿼리 - <if>

조건에 따라 특정 sql을 실행하도록 만들려면 <if> 태그를 사용할 수 있다. 여러 조건을 만족할 경우 조건을 만족한 부분의 sql 쿼리문들이 붙는다.

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

동적 쿼리 - <choose>

여러 조건을 만족할 수 있는 경우가 아니라면, <if>보다는 <choose>가 더 적합할 수 있다. choose는 조건을 만족하는 하나만 선택하여 sql을 추가하므로 훨씬 효율적이다.
<if>가 switch와 비슷하다면, <choose>는 if-else와 비슷하다.
이 상황에서는 검색 기능에서 옵션이 한 번에 하나만 들어오기 때문에 <if>가 아닌 <choose>로 받는 것이 적합하다.
<choose>에서는 만약 조건이 충족되지 않는다면 <otherwise>가 실행된다.

    <select id="searchResultCount" parameterType="SearchCondition" resultType="int">
        SELECT count(*)
        FROM springbasic.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>

이 때 % 는 와일드카드로, 'title%'는 title과 일치하는 값이거나 title 뒤에 n자의 문자가 더 올 수 있음을 의미한다. (0+)
만약 'title_' 이 왔다면, title과 일치하는 값은 허용되지 않고 title 뒤에 n개의 문자가 더 오는 값만 검색된다(1+).
MySQL은 _를 사용하고, Oracle은 ?를 사용한다.

동적쿼리 - <foreach>

한 번에 여러 결과를 반환하려는데 몇 개의 결과를 받을지가 정해져 있지 않은 경우에는 <foreach>를 사용한다. 조건은 IN으로 받는다.

<select id="getSelected" resultType="BoardDto">
    SELECT bno, title, content, writer, view_count, comment_count, reg_Date, up_date
    FROM springbasic.board
    WHERE bno IN
    <foreach collection="array" item="bno" open="(" close=")" separator=",">
        #{bno}
    </foreach>
    ORDER BY reg_date DESC, bno DESC
</select>

IN 뒤에 open과 close로 감싸이고 separator로 구분된 배열이 만들어져 쿼리문이 실행된다.


실습

우선 제목만 가지고 검색하는 기능을 만들어본다. 기본적인 쿼리를 성공시킨 후 동적 쿼리로 변경하기로 한다. 검색할 때 받는 옵션과 키워드는 SearchCondition이라는 객체로 묶어 넘겨주는 것으로 하였다.
먼저 mybatis-config.xml에 alias를 추가해준다.

<typeAlias alias="SearchCondition" type="com.fastcampus.ch4.domain.SearchCondition"/>

다음으로 sql 쿼리를 작성한다.

    <select id="searchSelectPage" parameterType="SearchCondition" resultType="BoardDto">
    SELECT bno, title, content, writer, view_count, comment_count, reg_Date, up_date
    FROM springbasic.board
    WHERE true
    AND title LIKE concat('%', #{keyword}, '%')
    ORDER BY reg_date DESC, bno DESC
    LIMIT #{offset}, #{pageSize}
    </select>

추가로 검색 결과 개수를 반환하는 쿼리도 추가해준다.

    <select id="searchResultCount" parameterType="SearchCondition" resultType="BoardDto">
        SELECT count(*)
        FROM springbasic.board
        WHERE true
        AND title LIKE concat('%', #{keyword}, '%')
        ORDER BY reg_date DESC, bno DESC
        LIMIT #{offset}, #{pageSize}
    </select>

다음으로 DAO에 새로운 쿼리를 호출하는 메서드를 추가해주고, BoardDao의 인터페이스에도 이를 추가해준다.

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

domain 패키지에 SearchCondition 클래스를 생성한다.

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

입력 값이 없을 때를 대비하여 기본값을 설정해준다. getter와 setter, 그리고 생성자 메서드를 함께 생성해준다 (생성자 메서드는 offset을 제외한다).


테스트

    @Test
    public void searchSelectPageTest() throws Exception{
        boardDao.deleteAll();
        for (int i=1;i<=20;i++){
            BoardDto boardDto = new BoardDto("title" + i, "content", "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);
    }

제목만 가지고 조회한 결과를 반환한다. 항상 같은 결과를 낼 수 있도록 db에 있는 데이터를 전부 지우고, 테스트를 실행할 때마다 새로 데이터를 넣어서 결과를 확인할 수 있도록 한다.
20개의 데이터를 만들어 집어넣었을 때, title2로 시작하는 제목의 게시글은 title2, title20 2개가 있으므로 assertTrue로 결과를 확인한다.
문제 없이 통과하는 것을 확인할 수 있었다.

searchResultCount()도 테스트하려는데 오류가 났다. 어떤 SQL문이 실행되고 있는지 확인하기 위해

  • log4jdbc 의존성,
  • log4jdbc.log4j2.properties,
  • logback.xml
  • root-context.xml 수정
    이 필요하다.

다시 실행해보면 실행되는 sql문들이 로그에 찍힌다. 보니 count를 구하는데 LIMIT와 ORDER BY가 붙어서 sql 오류가 발생하고 있었다. 이 부분을 삭제해주면 NullPointerException가 새롭게 발생한다.

> searchResultCount의 resultType을 int로 고쳐주고, SearchCondition을 받도록 바꿔주면 제대로 결과를 받는 것을 확인할 수 있었다.

post-custom-banner

0개의 댓글