java, spring

[Spring] 페이징 처리에 검색 기능 SQL 적용하기 (feat. Mybatis 동적 SQL)

isaac.kim 2021. 7. 24.
728x90
반응형

[Spring] 페이징 처리에 검색 기능 SQL 적용하기 (feat. Mybatis 동적 SQL)

이전 글

2021.07.17 - [Spring] - [Spring] 페이징 화면 처리 - 3 -

2021.07.17 - [Spring] - [Spring] 페이징 화면 처리 - 2 -

2021.07.17 - [Spring] - [Spring] 페이징 화면 처리 - 1 -

2021.07.16 - [Spring] - [Spring] 스프링에서 페이징 처리 (BoardController, BoardService 수정)

2021.07.14 - [Spring] - [Spring] MyBatis와 스프링에서 페이징 처리

 

지금까지 실습했던 SQL을 기준으로 봤을 때, 페이징 처리에 인라인 뷰를 이용하기 때문에 실제로 검색 조건에 대한 처리는 인라인 뷰의 내부에서 이루어져야 합니다.

 

검색 기준에는 단일 항목 검색, 다중 항목 검색이 있습니다.

먼저 '제목'을 기준으로 '테스트'라는 키워드가 들어간 글만 검색하는 SQL로 수정합니다. (단일 항목)

 

사용되는 데이터베이스 : MariaDB 10.5 version

 

기존 SQL

수정 SQL

 

수정 후 실행 결과

단일 항목 검색은 인라인 뷰(in-line view) 안쪽에서 필요한 데이터를 가져올 때 검색 조건이 적용되어야 하기 때문에 WHERE문 뒤에 검색 조건이 추가되고, ROWNUM 이 추가되도록 처리합니다.

 

title에 테스트라는 글자가 포함된 글만 불러와서 ROWNUM을 붙이는 SQL 부분만 떼어서 보면,

WHERE 조건에 맞게 검색된 항목에 rnum(ROWNUM)이 순차적으로 붙은 것을 확인할 수 있습니다.

다중 항목 검색

제목에 '테스트'라는 텍스트가 포함되어 있거나 내용에 '등록'이라는 텍스트가 포함된 것들을 조회합니다.

title에는 테스트, content에는 등록이라는 글자가 포함된 게시물을 조회한 결과

항목 검색을 테스트하기 위해 이와 같이 작성을 했습니다. 이제 사용자가 검색한 항목에 대해 처리할 수 있도록 SQL을 수정합니다.

 

MyBatis 동적 SQL 활용

MyBatis에는 SQL을 동적으로 사용할 수 있게 도와줄 태그들이 있습니다. MyBatis는 기존의 iBatis에서 발전하면서 복잡했던 동적 SQL을 작성하는 태그들이 많이 정리되어서 다음과 같이 몇 가지의 태그들만을 이용합니다.

 

MyBatis의 동적 태그들

if

choose(when, otherwise)

trim(where, set)

foreach

bind

 

<if> 태그는 test라는 속성과 함께 특정한 조건이 true가 되었을 때 포함된 SQL을 사용하고자 할 때 작성합니다.

<if test="type == 'w' ">

    (writer like '%' || #{keyword} || '%')

</if>

 

<choose> 태그는 여러 상황들 중 하나의 상황에서만 동작합니다. Java언어의 'if - else' 나 JSTL의 <choose>와 유사합니다.

<choose>
	<when test="type == 'T' ">
		(title like '%'||#{keyword}||'%')
	</when>
	<otherwise>
    	(title like '%'||#{keyword}||'%' OR content like '%'||#{keyword}||'%')
	</otherwise>
</choose>

<otherwise>는 when 태그의 모든 조건이 충족되지 않을 경우에 사용합니다.

 

 

trim, where, set은 단독으로 사용되지 않고, if, choose와 같은 태그들을 내포하여 SQL들을 연결해 주고, 앞 뒤에 필요한 구문들(AND, OR, WHERE 등)을 추가하거나 생략하는 역할을 합니다.

 

<where> 태그는 태그 안쪽에서 SQL이 생성될 때는 WHERE 구문이 붙고, 그렇지 않은 경우에는 생성되지 않습니다.

select * from tb_board
	<where>
    	<if test="seq_bno != null">
        	seq_bno = #{seq_bno}
        </if>
    </where>

위 같은 경우는 seq_bno 값이 null인 경우 where 구문이 없어지고, seq_bno 값이 존재하는 경우에는 'WHERE seq_bno = xx'와 같이 생성됩니다.

 

seq_bno 값이 존재하는 경우 : select * from tb_board WHERE seq_bno = 33

seq_bno가 null인 경우 : select * from tb_board

 

<trim>은 하위에서 만들어지는 SQL문을 조사하여 앞 쪽에 추가적인 SQL을 넣을 수 있습니다.

select * from tb_board
	<where>
    	<if test="seq_bno != null">
        	seq_bno = #{seq_bno}
        </if>
        <trim prefix="and" >
        	rnum = 1
        </trim>
    </where>

trim은 prefix, suffix, prefixOverrides, suffixOverrides 속성을 지정할 수 있습니다.

 

seq_bno 값이 존재하는 경우 : select * from tb_board WHERE seq_bno = 33 and rnum = 1

seq_bno가 null인 경우 : select * from tb_board WHERE rnum = 1

 

 

<foreach> 태그는 List, 배열, 맵 등을 이용해 루프를 처리할 수 있습니다. 주로 IN 조건에서 많이 사용하지만, 경우에 따라서는 복잡한 WHERE 조건을 만들 때에도 사용할 수 있습니다.

 

ex)

Java Side

Map<String, String> map = new HashMap<>();
map.put("T", "TTT");
map.put("C", "CCC");

작성된 맵을 파라미터로 전달하고, foreach를 이용하면 다음과 같은 형식이 가능합니다.

 

MyBatis Side (xml sql)

select * from tb_board

<trim prefix="where (" suffix=")" prefixOverrides="OR" >
	<foreach item="val" index="key" collection="map">
        <trim prefix="OR">
        	<if test="key == 'T'.toString()">
            	title = #{val}
            </if>
            <if test="key == 'C'.toString()">
            	content = #{val}
            </if>
        </trim>
    </foreach>
</trim>

foreach를 배열이나 List를 이용하는 경우에는 item 속성만을 이용하면 되고, Map의 형태로 key와 value를 이용해야 할 때는 index와 item 속성을 둘 다 이용합니다.

 

검색 조건 처리를 위한 Criteria의 변화

페이징 처리에 사용한 Criteria의 의도는 단순히 'pageNum'과 'amount'라는 파라미터를 수집하기 위해서입니다. 페이징 처리에 검색 조건 처리가 들어가면 Criteria 역시 수정이 필요합니다.

 

검색 조건(type)과 검색에 사용하는 키워드(keyword)가 필요합니다.

 

Criteria 클래스 수정

getTypeArr는 검색 조건이 각 글자(T, W, C)로 구성되어 검색 조건을 배열로 만들어서 한 번에 처리하기 위함입니다. getTypeArr( )을 이용해서 MyBatis의 동적 태그를 활용할 수 있습니다.

 

BoardMapper.xml에서 Criteria 처리

<foreach>를 이용해서 검색 조건들을 처리하는데 typeArr이라는 속성을 이용합니다. MyBatis는 원하는 속성을 찾을 때 getTypeArr( )과 같이 이름에 기반을 두어서 검색하기 때문에 Criteria에서 만들어둔 getTypeArr( ) 결과인 문자열의 배열이 <foreach>의 대상이 됩니다(MyBatis는 엄격하게 Java Beans의 규칙을 따르지 않고, get/set 메서드만을 활용하는 방식입니다.).

 

<choose> 안쪽의 동적 SQL은 'OR title ... OR content ... OR writer ... '와 같은 구문을 만들어내게 됩니다. 따라서 바깥쪽에서는 '<trim>'을 이용해서 맨 앞에서 생성되는 'OR'을 없애줍니다.

 

BoardMapperTests 클래스에서 테스트합니다.

 

BoardMapperTests 클래스

테스트 메서드(왼쪽)의 작성과 JUnit 실행 결과(오른쪽)

동적 쿼리로 만들어진 SQL 구문

 

 

검색 데이터의 개수 처리

동적 SQL을 이용해서 검색 조건을 처리하는 부분은 해당 데이터의 개수를 처리하는 부분에서도 동일하게 적용되어야만 합니다. 이 경우 가장 간단한 방법은 동적 SQL을 처리하는 부분을 그대로 복붙 할 수 있지만, 동적 SQL을 수정하는 경우에는 매번 목록을 가져오는 SQL과 데이터 개수를 처리하는 SQL 쪽을 같이 수정해야 합니다.

 

MyBatis는 <sql>이라는 태그를 이용해서 SQL의 일부를 별도로 보관하고, 필요한 경우에 include 시키는 형태로 사용할 수 있습니다.

 

검색조건의 분리 / 페이징 검색에 MyBatis include 태그 사용

<sql> 태그는 id라는 속성을 이용해서 필요한 경우에 동일한 SQL의 일부를 재사용할 수 있게 합니다.

 

게시물의 총개수를 구하는 select에 include 태그를 사용하여 검색 조건을 재사용할 수 있습니다.

getTotalCount에 대해서도 테스트를 진행해봅니다.

실제 쿼리로 뽑힌 것을 갖고도 dbms SQL 쿼리에서 데이터가 맞는지도 확인합니다.

작성했던 쿼리가 정상적인 값으로 확인됩니다.


내용이 많이 길어졌네요.

SQL단에서 페이징 처리와 검색 기능을 추가해보았고, MyBatis 동적 SQL 태그들을 사용하여 구현하고, 테스트까지 마쳤습니다. 다음은 화면에서 서버에 적용된 검색 기능을 활용할 수 있도록 화면을 수정하겠습니다.

 

728x90
반응형