[BE] mybatis 여러 개의 태그를 동시 조건으로 조회하기, #과 $의 차이

yoondgu·2022년 7월 24일
0

기능 구현

목록 보기
4/4

화면에서 태그를 선택하면, 해당 태그를 가지고 있는 강의를 조회한다.
하나의 강의는 여러 개의 태그를 가질 수 있다.
또 하나의 태그에 대해서 이를 가진 강의가 여러 개 있을 수 있다. (M:N)

검색조건에 따라 강의 정보를 조회하는 쿼리를 작성할 때
원래는 아래처럼 여러 개의 태그를 선택했을 때, 그 중 하나라도 해당이 되는 강의는 모두 조회하도록 했다.
(실제 코딩할 때 사용한 다른 검색조건들은 편의상 생략하고 기록한다.)

여러 개의 태그 조건 중 하나만 만족해도 조회하기

	<select id="getCoursesByCriteria" parameterType="kr.co.hta.criteria.CourseCriteria" resultMap="CourseResultMap">
		select *
		from online_courses
		<where>
			<if test="tags != null">
				course_no in (select course_no
								from online_course_tags
								where course_tag in
								<foreach collection="tags" item="tag" open="(" close=")" separator=",">
									#{tag}
								</foreach>)
			</if>
	</select>

하지만 여러 개의 태그를 선택했을 때, 그 태그들을 모두 가지는 강의만 조회하도록 하려면 어떻게 해야 할까?

아래와 같은 쿼리를 SQL developer에서 실행해보면,
'java'와 '객체지향' 태그를 모두 가진 강의번호만 조회할 수 있다.
이 때 having절의 2는 조건으로 삼는 태그의 수이다.
online_course_tags는 course_no, course_tag로 이루어진 테이블이기 때문에
태그 중 'java', '객체지향' 2개를 모두 가지는 특정 강의번호는 2행 이상 조회될 수밖에 없다.
마찬가지로 어떤 태그 조건 3개로 검색했다면 특정 강의번호는 최소 3행 이상 조회되어야 할 것이다.

select course_no
from (select course_no
    from online_course_tags
    where course_tag in ('java', '객체지향'))
group by course_no
having count(course_no) >= 2;

따라서 having절에 파라미터인 태그 collection의 size를 넣을 수만 있으면 mybatis에서도 이를 표현할 수 있다.

여러 개의 태그 조건을 모두 만족하는 경우만 조회하기

	<select id="getCoursesByCourseCriteria" parameterType="kr.co.hta.criteria.CourseCriteria" resultMap="courseResultMap">
		select *
		from online_courses
		<where>
			<if test="tags != null">
				course_no in (select course_no
								from (select course_no
									from online_course_tags
									where course_tag in
									<foreach collection="tags" item="tag" open="(" close=")" separator="," >
										#{tag}
									</foreach>)
								group by course_no
								having count(course_no) >= ${tags.size})
			</if>
	</select>	

파라미터가 CourseCriteria 객체이고, 이 객체는 필드로 List<String> tags 를 가지고 있다.
따라서 ${tags.size}와 같이 작성해서 쿼리의 일부를 완성시키면 된다.

#{}, ${} 와 collection의 size

처음에는 #{tags.size}로 작성했다가 java.lang.UnsupportedOperationException: null 오류가 발생했다.
헷갈렸던 두 가지의 차이를 다시 정리해보았다.

#{}, ${}의 차이점

#{}는 preparedStatement 방식으로 파라미터값을 바인딩해준다. 문자열에 자동으로 따옴표가 붙는다.
${}는 sql문의 일부로, 값이 그대로 들어가는 것이다. 문자열에 따옴표가 붙지 않는다. => order by 절의 컬럼명을 동적으로 지정하는 등에 활용할 수 있다.
하지만 ${}는 악의적인 쿼리 주입을 예방할 수 없어서, 사용자 입력값을 전달할 때는 사용하지 않는 게 좋다.

지금 상황에는 having절에서 ?으로 값을 바인딩할 수 있는 것이 아닌가? 의문이 들었다.
그리고 이 경우에는 넣고자 하는 값이 문자열이 아니라 숫자이기 때문에 #{}를 쓴다고 해서 자동 따옴표가 붙어 문제가 생기는 건 아니었다.
#과 $의 파싱 방식 차이는 이유가 아니고, #{}로는 .size로 콜렉션의 size를 구할 수 없기 때문이었다.
예외명에서 말하듯이 말 그대로 UnsupportedOperation 이라는 것.

https://stackoverflow.com/questions/58654012/collection-size-in-mybatis-query
이 글에서의 답변을 나는 아래처럼 이해했다.

tags.size는 콜렉션 객체에서 size() 메소드를 호출시키도록 해주는 OGNL(Object-Graph Navigation Language) 표현식으로 작성한 것이다.
#{}는 mybatis 내부 표현식의 값과 필드를 getter/setter/기본생성자 메소드를 통해 접근하는 것이지 size() 메소드를 지원하지는 않는다.

그런데 그렇다면, 원하는 결과는 얻을 수 있지만 ${}의 사용으로 인해 보안은 굉장히 취약한 쿼리라는 점에서 고민이 필요하기도 한 것 같다.

0개의 댓글