Kim VamPa

[스프링 게시판][7] 주제별 검색 기능 구현 본문

스프링 프레임워크/게시판 프로젝트

[스프링 게시판][7] 주제별 검색 기능 구현

Kim VamPa 2021. 3. 8. 10:30
728x90
반응형

Git 주소 : github.com/sjinjin7/Blog_BoardProject

목표

 

주제별 검색 기능 구현

 

 주제별로 검색 할 수 있는 기능 구현을 목표로 합니다. "코드로 배우는 스프링 웹 프로젝트(남가람 북스)"를 참고하였습니다.

 

 

순서

1. 시작 전

2. 사용할 쿼리

3. 'type'변수 추가(Criteria.java)

4. BoardMapper.xml 쿼리 수정

5. View 처리

6. get.jsp, modif.jsp

7. 테스트

 

 

 

 

 

1. 시작 전

 다음의 '제목 검색', '내용 검색', '작성자 검색', '제목 + 내용 검색', '제목 + 작성자 검색', '제목 + 내용 + 작성자 검색' 주제로 검색 옵션을 부여할 것입니다. 따라서 기존 검색 기능의 경우 뷰로부터 'keyword'데이터만 서버에 전송을 하였지만 어떠한 주제로 사용자가 검색을 하는지에 대한 정보인 'type' 데이터도 같이 전송해주어야 합니다.

 

 서버와 DB 단에서 핵심은 6개의 검색 주제에 따라 각각의 쿼리가 실행되도록 하는 것입니다. 가장 단순한 방법은 6개의 쿼리와 그 쿼리를 호출하는 6개의 메서드를 Mapper, Service 단에서 작성하고 Controller에서 type에 따라 분기하여 각 메서드를 호출하는 것입니다. 하지만 이번 포스팅에선 BoardMapper.xml에서 6개의 쿼리문을 작성하는 것이 아니라 MyBatis에서 제공하는 태그(foreach, choose, when, trim)를 활용하여 동적으로 변화하는 하나의 쿼리문만을 작성하여 구현할 것입니다.

 

 따라서 <trim>, <foreach>, <choose>, <when>, <sql>, <include> 태그에 대한 이해가 필요합니다. 잘 모르신다면 검색을 통해 찾아보시거나 아래의 포스팅을 참고해주세요.

 

[MyBatis] <sql>, <include> 사용법

[MyBatis] <choose>, <when>, <otherwise>

[MyBatis] <trim> 사용법

[MyBatis] <foreach> 사용법

 

 

 

2. 사용될 쿼리

Oracle

 

그림 2-1

 

 기존 검색 쿼리에서 조건문 중 위에서 표시한 부분만 변경합니다. 각 상황에 따라 그림 2-1에서 표시한 부분이 아래와 같이 작성됩니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
    -- 제목
    title like '%검색%'
    -- 내용
    content like '%검색%'
    -- 작성자
    writer like '%검색%'    
    -- 제목 + 내용
    (title like '%검색%' OR content like '%검색%')
    -- 내용 + 작성자
    (content like '%검색%' OR writer like '%검색%')
    -- 제목 + 내용 + 작성자
    (title like '%검색%' OR content like '%검색%' OR writer like '%검색%')
 

 

 소괄호"()"를 붙여준 이유는 where문에서 and 연산자가 사용되고 있는데 이는 OR연산자보다 우선순위가 높아서 의도와 다르게 동작하는 것을 예방하기 위함입니다.

 

 

MySQL

 

그림 2-2

 

 MySQL에서는 위의 표시 부분만 아래와 같이 변경됩니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
    -- 제목
    title like '%검색%'
    -- 내용
    content like '%검색%'
    -- 작성자
    writer like '%검색%'    
    -- 제목 + 내용
    title like '%검색%' OR content like '%검색%'
    -- 내용 + 작성
    content like '%검색%' OR writer like '%검색%'
    -- 제목 + 내용 + 작성자
    title like '%검색%' OR content like '%검색%' OR writer like '%검색%'
 

 

 MySQL쿼리문에서는 조건문에 AND연산자가 없어서 소괄호를 붙여주지 않았습니다.

 

 

 

3. 'type'변수 추가(Criteria.java)

 뷰로부터 사용자가 어떠한 주제로 검색하는지를 알기 위해서 Criteria 클래스에 'type'변수를 추가합니다. 

 

1
2
3
4
 
    /* 검색 타입 */
    private String type;
 

 

 type변수에  담길 데이터는 "T"(제목), "C"(내용), "W"(작성자), "TC"(제목 + 내용), "TW"(제목 + 작성자), "TCW"(제목 + 내용 + 작성자)입니다. 그런데 제가 사용할 방식에서는 "T", "C", "W"와 같이 문자 하나가 저장된 데이터가 필요로 합니다. 따라서 "TC", "TW", "TCW" 데이터들이 문자 하나씩 저장된 데이터가 될 수 있도록 배열로 변환해주는 작업을 해줍니다.

 

 Stirng 배열 타입 typeArr을 선언해줍니다.

 

1
2
3
4
 
    /* 검색 타입 배열 */
    private String[] typeArr;
 

 

그림 3-1

 

그림 3-2

 

 두 변수 모두 getter/setter 메서드를 추가해줍니다. 그리고 기존 toStirng 메서드를 지우고 추가한 type, typeArr변수를 포함한 toString 메서드를 추가해줍니다.

 

그림 3-3

 

 

 type변수에 데이터가 들어왔을 때 자동으로 배열 형식으로 변환하여 typeArr변수에 저장될 수 있도록 setType() 메서드를 수정해줍니다. (배열로 변환하기 위해서 String 타입의 데이터를 String 배열 타입 데이터로 변환해주는 split() 메서드를 사용하였습니다.)

 

그림 3-4

 

 

 

4. BoardMapper.xml 쿼리 수정

 수정되어야 할 부분은 getListPaging() 메서드 쿼리와 getTotal() 메서드 쿼리문의 조건문(where)입니다. 그리고 새롭게 삽입될 조건은 2개 메서드 동일합니다. 그렇기 때문에 중복된 코드를 줄이기 위해서 <sql>, <inlcude> 태그를 사용하였습니다.

 

 아래는 Oracle, MySQL 각각의 수정 후의 코드입니다.(<sql> 태구 추가, getListPage()와 getTotal() 메서드 수정) 코드르 이해하기 위해선 <sql>, <include>, <trim>, <choose>, <when>, <foreach>에 대한 공부가 필요합니다.

 

Oracle

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
 
    <!-- 검색 조건문 -->
    <sql id="criteria">
        <trim prefix="AND (" suffix=")" prefixOverrides="OR">
            <foreach collection="typeArr" item="type">
                <trim prefix="OR">
                    <choose>
                        <when test="type == 'T'.toString()">
                            title like '%'||#{keyword}||'%' 
                        </when>
                        <when test="type == 'C'.toString()">
                            content like '%'||#{keyword}||'%' 
                        </when>
                        <when test="type == 'W'.toString()">
                            writer like '%'||#{keyword}||'%' 
                        </when>
                    </choose>
                </trim>
            </foreach>
        </trim>
    
    </sql>
 
 
 
    <!-- 게시물 목록(페이징) -->
    <select id="getListPaging" resultType="com.vam.model.BoardVO">
    
    <![CDATA[
        
        select bno, title, content, writer, regdate, updatedate from(
        
                select /*+INDEX_DESC(vam_board pk_board) */ rownum  as rn, bno, title, content, writer, regdate, updatedate
                  
                from vam_board where rownum <= #{pageNum} * #{amount} 
    ]]>            
                <if test="keyword != null">
                    <include refid="criteria"></include> 
                </if>
    
    <![CDATA[
                    
                )
                    
        where rn > (#{pageNum} -1) * #{amount}
    
    ]]>
    
    </select>
 
 
    <!-- 게시물 총 개숫 -->
    <select id="getTotal" resultType="int">
    
        select count(*) from vam_board
        
        <if test="keyword != null">
        
            where bno > 0 <include refid="criteria"></include>
        
        </if>
    
    </select>    
 
 

 

그림 4-1

 

 

 <if> 태그를 한번더사용한 이유는 검색조건없이 화면을 이동함으로써 값이 없는 typeArr 변수가 생성되고 BoardMapper 로 전달이 되는데 이로인해 에러가 발생함을 방지하기 위해 작성하였습니다. 아래의 <if>태그를 작성한 이유도 동일합니다.

 

 

 bno>0은 아무 의미 없는 조건입니다. 그럼에도 작성한 이유는 <include> 태그에 의해 삽입될 쿼리문이 and() 형식이기 때문에 구문 오류를 방지하기 위해서 작성하였습니다. 

 

 

 

MySQL

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
 
    <!-- 검색 조건문 -->
    <sql id="criteria">
        <trim prefix="where (" suffix=")" prefixOverrides="OR">
            <foreach collection="typeArr" item="type">
                <trim prefix="OR">
                    <choose>
                        <when test="type == 'T'.toString()">
                            title like concat('%',#{keyword},'%') 
                        </when>
                        <when test="type == 'C'.toString()">
                            content like concat('%',#{keyword},'%') 
                        </when>
                        <when test="type == 'W'.toString()">
                            writer like concat('%',#{keyword},'%' )
                        </when>
                    </choose>
                </trim>
            </foreach>
        </trim>
    
    </sql>
 
 
 
    <!-- 게시물 목록(페이징) -->
    <select id="getListPaging" resultType="com.vam.model.BoardVO">
    
        select * from (
                select bno, title, writer, regdate, updatedate  
                from vam_board 
                <if test="keyword != null">
                    <include refid="criteria"></include>
                </if>
                order by bno desc) as T1
        limit #{skip},#{amount}
    
    </select>
 
 
    <!-- 게시물 총 개숫 -->
    <select id="getTotal" resultType="int">
    
        select count(*) from vam_board
        
        <if test="keyword != null">
            <include refid="criteria"></include>
        </if>    
    
    </select>    
 
 

 

 

 

 

 

 

5. View 처리

 사용자가 검색 옵션을 선택할 수 있도록 인터페이스를 추가해줍니다. class 속성 값이 'search_area'인 <div> 태그에 아래의 코드를 추가합니다.

 

1
2
3
4
5
6
7
8
9
10
11
 
            <select name="type">
                <option value="" <c:out value="${pageMaker.cri.type == null?'selected':'' }"/>>--</option>
                <option value="T" <c:out value="${pageMaker.cri.type eq 'T'?'selected':'' }"/>>제목</option>
                <option value="C" <c:out value="${pageMaker.cri.type eq 'C'?'selected':'' }"/>>내용</option>
                <option value="W" <c:out value="${pageMaker.cri.type eq 'W'?'selected':'' }"/>>작성자</option>
                <option value="TC" <c:out value="${pageMaker.cri.type eq 'TC'?'selected':'' }"/>>제목 + 내용</option>
                <option value="TW" <c:out value="${pageMaker.cri.type eq 'TW'?'selected':'' }"/>>제목 + 작성자</option>
                <option value="TCW" <c:out value="${pageMaker.cri.type eq 'TCW'?'selected':'' }"/>>제목 + 내용 + 작성자</option>
            </select>    
 

 

그림 5-1

 

<c:out>의 3항 연산 경우 기본적으로 상황에 맞는 <option>이 기본적으로 선택되도록 하기 위함입니다. 예를 들어서 '제목' 검색을 통해 페이지를 이동하였을 때 '제목'옵션이 선택되어 있도록 하기 위함입니다. 

 

 

 

 서버에 type 데이터를 전송하기 위해서 <form> 태그 내부에 <input> 태그를 추가합니다.

 

1
2
3
 
<input type="hidden" name="type" value="${pageMaker.cri.type }">
 

 

그림 5-2

 

 

 

 

 기존 검색 버튼을 누를 시 작동하도록 하는 JS코드를 아래와 같이 수정합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
    $(".search_area button").on("click"function(e){
        e.preventDefault();
        
        let type = $(".search_area select").val();
        let keyword = $(".search_area input[name='keyword']").val();
        
        if(!type){
            alert("검색 종류를 선택하세요.");
            return false;
        }
        
        if(!keyword){
            alert("키워드를 입력하세요.");
            return false;
        }        
        
        moveForm.find("input[name='type']").val(type);
        moveForm.find("input[name='keyword']").val(keyword);
        moveForm.find("input[name='pageNum']").val(1);
        moveForm.submit();
    });
 

 

그림 5-3

 

 if문의 경우 '검색 옵션'고 '키워드'를 입력하지 않고 버튼을 누르지 못하도록 하기 위해 사용하였습니다. 옵션 선택 혹은 키워드를 입력하지 않으면 버튼이 동작하지 않고 경고창이 동작하게 됩니다.

 

 

 

 아래의 CSS설정을 추가합니다.

 

그림 5-4

 

그림 5-5

 

 

 

6. get.jsp, modify.jsp

 이전 포스팅 "[스프링 게시판][6] 페이징 기능 구현(페이지 이동 인터페이스) -2"과 같이 '조회(getj.sp) 화면'과 '수정(modify.jsp) 화면'에서 기존의 '목록(list.jsp) 화면'으로 이동할 수 있도록 각각의 <form> 태그 내부에 아래의 <input> 태그를 추가합니다.

 

그림 6-1

 

그림 6-2

 

 

 

7. 테스트

 아래의 항목들을 테스트합니다.

1) 검색 없이 페이지 이동이 되는지 테스트

2) 경고창 작동 테스트

3) 검색 후 이동 테스트

4) 조회(get.jsp), 수정(mdofiy.jsp)에서 기존 목록 화면(list.jsp) 이동 테스트

 

 

 

 

 

REFERENCE

 

 

DATE

  • 2020.03.08
728x90
반응형
Comments