BackEnd/WEB

mybatis_검색기능_국비Day74

Leo.K 2022. 6. 16. 15:39

지난 시간에는 기존에 실습한 방명록 파일에 대한 DB접근을 mybatis ORM을 사용하도록 수정하는 작업을 거쳤다. 소스파일은 필자의 깃허브 주소에 있으니 내려받아서 DB접근을 mybatis로 수정하고, 검색기능을 추가하는 실습을 함께 따라해보면 좋을 것 같다. 소스코드는 여기에서 확인하길 바란다.

[ visit_list.jsp ]

먼저, 모든 방명록을 한 곳에 리스트로 출력한 파일을 수정할 것이다. 이 부분에 검색을 할 수 있도록 ui를 다음과 같이 추가해주자. 어느 위치에 코드를 추가할지 인식할 수 있도록 하겠다.

15행에 value값으로 ${param.search_text}를 출력해주었는데, 이는 검색조건으로 자기 자신을 호출한 경우 정보 유지를 위해 사용한 것으로 자세한 설명은 밑에서 계속 하도록 하겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<h1 id="title">::::방명록::::</h1>
<div style="margin-top: 10px; margin-bottom: 10px; text-align: right;">
    <input class="btn btn-primary" type="button" value="글쓰기" onclick="location.href='insert_form.do'">
</div>
 
 
<!-- 검색메뉴 -->
<div style="text-align: center; margin: 10px;">
    <select id="search">
        <option value="all">전체</option>
        <option value="name">이름</option>
        <option value="content">내용</option>
        <option value="name_content">이름+내용</option>
    </select>
    <input id="search_text" value="${param.search_text}">
    <input type="button" value="검색" onclick="search();">
</div>
 
 
<!-- 게시물이 없다면? -->
<c:if test="${empty list }">
    <div id="empty_message">게시물이 없습니다.</div>
</c:if>
cs

 

다음으로는 검색어를 입력하고 버튼을 누르면 검색이 되도록 제이쿼리를 이용하여 자바스크립트 함수를 만들어 줄것이다.

이때, 한가지 고려해 줄 사항이 있다. 검색 필터를 적용하는 과정에서 "전체"를 선택하는 경우에는 별다른 검색어를 입력하지 않아도 검색이 가능하다. 하지만 나머지 경우에는 반드시 검색어를 입력해야 입력한 값을 파라미터로 넘겨서 검색이 가능하기 때문에 검색 조건이 "전체"가 아닌 경우에만 입력값에 대한 유효성을 체크해줘야 한다.

입력값에 대한 유효성 검사를 통과했다면 실제로 데이터를 보내주어야 한다. 데이터를 ajax를 사용해 파라미터를 보내도 되겠지만, 필자는 오늘 다른 방법을 사용해볼 것이다. 단순하게 자기 자신을 파라미터만 주어서 다시 호출하는 것이다. 간단한 방식을 두고 왜 복잡하게 하는지 묻는다면, 공부하는 기간에 좀 더 다양한 경우의 수를 접해보고자 함이다.

이렇게 하는 경우 해결해야할 세 가지의 문제점이 있다. 그 경우의 수를 직접 보고 해결해 나가는 방법을 알아보자.

1. 검색어 인코딩 처리

당연히 검색어를 영어로만 입력하게 제약을 걸 수는 없다. 검색어는 숫자, 문자, 특수문자, 한글 등 모든 문자를 사용할 수 있어야 하는데, 이때, 한글과 특수문자는 파라미터를 인코딩 처리를 하지않고 데이터를 전송하면, 서버쪽에서 수신할때, 데이터가 깨지기 때문에, 올바른 수신이 되지 않는다. 따라서 인코딩 처리를 해주어야 하는데, 이 시점에서 자바 스크립트와 자바에서 사용하는 인코딩함수를 한 번 짚고 넘어가겠다. 

자바 : URLEncoder.encode()

자바스크립트 : encodeURIComponent()

2. 새로고침 했을 때, 값이 지워지는 경우

위에서도 말했듯이 자기 자신을 호출하기 때문에 파라미터 값이 있다면 그 값을 검색 조건과 검색어에 ${param.value}를 사용해서 입력해주면, 사용자가 입력한 값이 유지되는 것처럼 할 수 있다. 하지만, URL에 파라미터 값이 없다면, ${param.value}값이 null이 되기 때문에 검색 조건과 검색어가 비어버리는 경우가 발생한다. 이때는, 파라미터의 값이 비어있지 않은 경우, 즉 파라미터 값이 있을 때만,  그 값을 채워 넣어 주는 식으로 초기화 처리를 해주면 된다. 물론 document에 모든 element가 배치된 상태로 진행해야 하기 때문에 제이쿼리를 초기화하는 함수 내부에 정의해야 한다.

ajax를 사용했다면, 사용자가 입력한 값과 별개로, 출력되는 리스트의 결과만 검색어에 따른 필터링을 거쳐서 이러한 작업을 처리할 필요가 없지만, 자기 자신을 호출하기 때문에 이러한 정보 유지 기술이 필요한 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script type="text/javascript">
    $(document).ready(function(){
        if("${not empty param.search}" == "true"){
            //param.search가 비어있지 않다면 값이 있을 때만 바꾸고 없을땐 바꾸지 마라
            //없을때도 이 코드가 적용되면, param.search의 값이 null이므로 기본값인 "전체"도 선택되지 않는다;; 
            $("#search").val('${param.search}');
        }
        
        //전체 검색 실행시 검색어 지우기
        if("${param.search eq 'all' }" == "true"){
            $("#search_text").val('');
        }
    });
</script>
cs

3. 전체 검색시 검색어 지우기 

위의 코드를 보면 추가되는 내용이 있다. 반드시 해야할 작업이 아닌 선택적인 사항이다. 전체 검색을 하는 경우 굳이 검색어가 필요 없기 때문에, 위의 코드를 따라 검색어에 입력된 값을 지워준다. 

 

전체적인 자바스크립트의 코드는 아래와 같다. 

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
<script type="text/javascript">
    $(document).ready(function(){
        if("${not empty param.search}" == "true"){
            //param.search가 비어있지 않다면 값이 있을 때만 바꾸고 없을땐 바꾸지 마라
            //없을때도 이 코드가 적용되면, param.search의 값이 null이므로 기본값인 "전체"도 선택되지 않는다;; 
            $("#search").val('${param.search}');
        }
        
        //전체 검색 실행시 검색어 지우기
        if("${param.search eq 'all' }" == "true"){
            $("#search_text").val('');
        }
    });
 
    function search(){
        var search      = $("#search").val();
        var search_text = $("#search_text").val().trim();
        
        //전체 검색이 아닌경우 
        if(search != 'all' && search_text ==''){
            
            alert("검색어를 입력하세요.!!");
            $("#search_text").val("");
            $("#search_text").focus();
            return;
        }
        
        //검색내용을 parameter로 목록 보기(list.do)호출                
        //검색어는 한글, 특수문자 가능 따라서 인코딩 필수!!
        //인코딩 -> 자바 : URLEncoder.encode(), 자바스크립트 : encodeURIComponent()  
        location.href="list.do?search=" + search + "&search_text=" + encodeURIComponent(search_text);
    
        //파라미터를 ajax로 보낸다면 비동기로 데이터를 송수신하므로 입력값은 유지가 되고 출력되는 리스트만 바뀌지만
        //지금은 자기자신에 파라미터를 주어서 호출한 것이기 때문에, 검색 조건에 맞는 출력은 제대로 될지 몰라도 
        //사용자가 입력한 데이터가 리프레시 되면서 삭제될 수 있다, 따라서 그 값을 유지하기 위해 파라미터로 넘어온 데이터를 
        //사용작가 입력한 데이터가 남아있는 것처럼 유지할 수 있도록 설정해야 한다.
        
    }
</script>
cs

 

이제 클라이언트 쪽에서 파라미터를 서버로 송신하고, 그에 따라 제약사항을 설정해주었다. 그렇다면 서버단에서 파라미터를 수신하고 처리하는 과정을 보도록 하자

[ VisitListAction ]

누누히 말하지만 자기 자신을 호출하기 때문에, 같은 페이지 상에서도 사용자가 사용하기에 따라 요청되는 URL은 여러가지 이지만, 이 URL을 수신하는 서블릿은 하나이기 때문에 경우의 수를 살펴보고 그에 따른 적절한 처리를 해주어야 한다.

요청 URL 종류

  1. /visit/list.do
  2. /visit/list.do?search=&search_text=
  3. /visit/list.do?search=all&search_text=
  4. /visit/list.do?search=name&search_text=길동
  5. /visit/list.do?search=content&search_text=길동
  6. /visit/list.do?search=name_content&search_text=길동

2의 경우 클라이언트단에서 검색조건은 서버로 전송하기전에 기본적으로 선택되도록 설정해주었지만, 혹시라도 URL을 직접 조작할 수 있는 사용자가 존재한다면,,, 그에 대한 방어를 위한 것이다. 파라미터를 수신했는데, 1번이나, 2번인 경우에 대해서는 값이 없기 때문에 적절한 조치를 취해주어야 한다. 

1의 경우 -> 사용자가 검색을 진행하지 않은 가장 처음이다. 이때는 전체 검색을 진행한다.  ->  (search == null)

2의 경우 -> 파라미터는 들어왔지만, 짖궂은 사용자가 값을 지워버린 경우이다.. 즉, 객체는 들어왔는데 그 내부가 비어있는 null객체가 아니라 빈 객체인것이다. -> ( search.isEmpty() ) 

위의 두 가지 경우의 수를 묶어서 조건으로 처리하고 만족하는 경우 search = "all"을 주어서 전체 검색을 진행시킨다.

1
if(search == null || search.isEmpty()) search = "all";
cs

 

위의 조건을 통과했다면,  mybatis에서 실행할 수 있도록, mapper를 작성해 줄 건데, mapper에게 전달해 줄 map을 포장해야 한다. 이 또한 수신한 파라미터의 경우의 수에 따라서 조건으로 나누어서 다르게 map을 포장해줄 것이다. 마지막에 조건을 else를 사용하여 단순하게 표현할  수 있지만, 처음 공부하는 입장에서 코드의 가독성을 높이기 위해서 자세하게 조건을 직접 쓰면서 나누었다.

조건을 세분화 하면 다음과 같다. 

  1. 만약 검색조건이 "전체"가 아니라면 
    1. 만약 검색 조건이 이름 + 내용 이라면 
      1. 이름과 내용 모두에서 검색어를 검색할 수 있도록 map에 저장한다.
    2. 만약 검색 조건이 이름이라면
      1. 이름에서 검색어를 검색할 수 있도록 map에 저장한다.
    3. 만약 검색 조건이 내용이라면
      1. 내용에서 검색어를 검색할 수 있도록 map에 저장한다.
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
//1. 수신 인코딩
request.setCharacterEncoding("utf-8");
 
//2. 파라미터 수신
String search       = request.getParameter("search");
String search_text  = request.getParameter("search_text");
 
//아에안들어왔거나(1)     객체(파람)은왔는데 비어있는 경우(2)        => 전체 조회해라.
if(search == null       || search.isEmpty()) search = "all";
 
//검색조건을 담을 Map
Map map = new HashMap();
 
if(!search.equals("all")) {//전체 검색이 아닌 경우에만 map에 데이터를 저장해서 검색조건을 준다.
    
    if(search.equals("name_content")) { //이름 + 내용
        
        map.put("name"   , search_text);
        map.put("content", search_text);
    }else if(search.equals("name")) {    //이름 검색
        map.put("name"   , search_text);
        
    }else if(search.equals("content")) {    //내용 검색
        map.put("content", search_text);
    
    }
    
}
cs

 

[ visit.xml ] - 동적쿼리!

이제 mybatis가 인식할 수 있도록 mapper에 쿼리를 작성해줄 것인데, 위에서 본 것과 마찬가지로 조건에 따라서(사용자가 검색한 조건에 따라서) map에 저장되는 정보가 다르기 때문에 쿼리로 다르게 작성되어야 한다. mybatis를 사용하지 않았다면 경우의 수마다 dao함수를 다르게 만들어 줘야 하지만, mybatis는 동적쿼리를 지원하기 때문에 이를 사용하는 방법을 정리해보도록 하겠다. 

먼저 동적쿼리란, 단순하게 조건에 따라 쿼리를 추가할 수도, 제외할 수 있도록 하는것이다. 이를 사용하기 위해 알아두어야 할 기본적인 내용만 정리하겠다. 먼저 <trim>태그를 사용하는데, 흔히 알고 있는 trim의 기능은 공백을 제거하는 기능이다. "제거를 한다"라는 기능은 맞다. 기본값이 공백으로 설정되어 있기 때문에 이렇게 사용해왔던 것 뿐이다. 그 말은 제거하는 내용을 오버라이드 하여 직접 지정해줄 수 있다는 것이다. 이는 prefixOverrides=""라는 속성을 사용해서 설정한다.

그 다음으로는 prefix라는 속성이 있는데, 단어 뜻 그래도 접두어라는 말이다. 이 속성을 설정하면 명령어를 작성할 때, 명령어의 내용만 작성하면, 조건에 맞는 경우에만 prefix에 지정한 접두어가 명령어의 가장 앞에 삽입된다. 

이 또한 경우의 수를 나누어서 하나씩 살펴보도록 하자. 위에서 작성한 경우의 수를 그대로 가져와서 설명하겠다. 조건과 아래의 이미지를 함께 보면서 이해하면 좋을 것 같다. 가독성을 위해 리스트는 제거하였다. 

1. 만약 검색조건이 "전체"가 아니라면

검색조건이 전체라면 조건을 만족하는 내용이 없기때문에,  map에는 아무런 데이터도 포장되지 않은 빈 map이 파라미터로 전달이 된다. 이러한 경우 mapper에서도 조건을 만족하지 않기 때문에 기본적인 쿼리문이 실행된다.

select * from visit order by idx desc -> 이는 단순히 테이블의 모든 레코드를 조회하는 것이다.                                     

이 부분에서 <if test="name!=null">인 부분이 있는데 이는 전달받은 map에 "name"이라는 키값이 존재하는지에 대한 여부를 검사하는 것이다. 자바에서는 쉽게 map.contains("name")으로 생각하면 좋겠다. 결과적으로 지금 같은 경우에는 아무것도 포장되지 않았다. 해당 조건을 만족하는 경우에만 쿼리문이 추가가 되는 것이고, 만족하지 않으면 쿼리가 추가되지 않고, 넘어간다. 이때 만약에 명령이 추가가 된다면, trim태그에서 설정한 prefix속성인 where가 붙어서 적용된다. 자세한 내용은 뒤에서 이어서 설명하겠다. 

2. 만약 검색 조건이 이름 + 내용 이라면 

위에서 이름과 내용 모두에서 검색어를 검색할 수 있도록 map에 저장했다. 이 경우에는 동적쿼리에서 저장한 모든 조건을 만족하기 때문에 다음과 같은 쿼리가 실행된다. 

1
2
3
select * from visit
where name like '%' || #{name} || '%' or content like '%' || #{content} || '%'
order by idx desc
cs

3. 만약 검색 조건이 이름이라면

이름에서 검색어를 검색할 수 있도록 map에 저장한다. 이 경우에는 첫 번째 조건만 만족하고 다음 조건은 만족하지 않기 때문에 첫 번째 명령문만이 포함되어 다음과 같은 명령이 실행된다.

1
2
3
4
select * from visit
where name like '%' || #{name} || '%'
order by idx desc
cs

4. 만약 검색 조건이 내용이라면

내용에서 검색어를 검색할 수 있도록 map에 저장한다. 사실 이 경우의 수를 대비해서 prefixOverrides속성을 설정해준 것이다. 이 경우에는 name조건은 만족하지 않고, content조건만 만족하기 때문에 아래와 같은 명령문이 동적으로 완성된다.

1
2
3
select * from visit
where or content like '%' || #{content} || '%'
order by idx desc
cs

조건만 만족하면 그 내부에 작성한 명령이 그대로 추가된다고 하지 않았는가? 하지만 이런식으로 추가가 되면, Syntax Error가 발생한다. 이를 방지하기 위해 필자가 처음 도입부분에 설명한 내용을 참고하면 좋을 것 같다. trim태그의 기능인 삭제 기능의 기본값이 공백으로 설정되어 있는데, 이를 or를 삭제하도록 재정의해주는 것이다. 이렇게 하여 where태그 뒤에 or가 등장한다면 그 or를 삭제하여 명령을 다음과 같이 실행한다. 물론 or 대신 and를 사용할 수도 있고, any등을 사용할 수 있으며 |을 사용하여 "or|and" 이런식으로 두개 이상으로 재정의할 수도 있다.

1
2
3
select * from visit
where content like '%' || #{content} || '%'
order by idx desc
cs

 

'BackEnd > WEB' 카테고리의 다른 글

MVC_국비Day79  (0) 2022.06.23
MyBatis_SubQuery_국비Day75  (0) 2022.06.17
mybatis_국비Day73  (0) 2022.06.15
ORM_DB프레임워크_국비_DAY72  (0) 2022.06.14
XML_Naver검색API_국비DAY71  (0) 2022.06.13