MyBatis XML의 Parameter 기호인 $ 와 # 은 어떤 차이가 있나 ?

 

 

      MyBatis Framework을 사용해서 개발하다 보면 SQL Mapping Parameter를 대체하는 방식으로 # (Hash) 와 $ (Dollar)를 사용하게 된다.   그런데 막상 그 차이에 대해서 명확하게 알지 못하고 그냥 남들이 썼던 Code를 보고 따라하다 보니... 그냥 사용하고 있다는 사람들을 종종 보게 된다.   개발자가 무심코 작성한 코드가 보안상 취약점이 되어 뒷통수를 때리는 경우가 발생 할 수 있으니 정확한 차이와 사용처에 대해서 짚고 넘어가고자 몇자 적어본다.

 

   MyBatis에서 '#' 과 '$'는 SQL Mapping에서 Parameter를 대체하는 방식에서 차이가 있다.   이 두 기호는 Parameter를 SQL Query에 삽입하는 데 사용되지만 보안적 측면과 성능적 측면에서 아주 중요한 차이를 보인다.

 

# (Hash)

   '#'는 SQL Query에서 Parameter를 안전하게 Binding 하는 데 사용된다.   MyBatis는 #을 사용하여 제공된 값의 Type에 관계 없이 처리할 수 있도록 Query를 제공한다.
   예를 들어 사용자명 Column에서 사용자 이름을 조건으로 조회하는 Query를 살펴보자.

<select id="selectUser" resultType="User">
  SELECT * FROM users WHERE username = #{username}
</select>

   - #{username}은 안전하게 SQL Query에 Binding 하게 된다.   #{username}에 대해서 그 값이 그대로 노출되지 않고 ?로 Binding 되어 Parsing 되는 것이다.
   - SQL Query는 'SELECT * FROM users WHERE username = ?' 로 변환이 된 후 Parameter 값이 준비된 Query에 Binding 된다.
   - 즉, username에 '이순신'이라는 Parameter를 # 기호를 사용하여 작성하게 되면, 최종적으로 실행되는 Query는 "SELECT * FROM users WHERE username ='이순신' " 이 된다.   살짝 헤깔리기 쉬운 부분인데 [이순신]이라는 단어가 String으로 받아들여져서 자동으로 '(작은 따옴표)를 작성해서 전달한다는 차이이다.
   - 이 방식은 SQL Injection 공격으로부터 예방 할 수 있는 유용한 방식이다.

 

$ (Dollar)

   '$'는 SQL Query에서 Parameter를 직접 대체하는 방식이다.   이 방식은 Table Partition 을 사용하는 것처럼 Table 명을 Parameter로 전달해서 사용하는 경우 같이 Query의 동적 생성에는 유리하지만 반대로 SQL Injection 공격에는 취약할 수 밖에 없다.
   위와 동일한 예를 들어보자.

<select id="selectUser" resultType="User">
  SELECT * FROM users WHERE username = ${username}
</select>

   - ${username}는 Parameter로 요청되는 값을 그대로 직접 대체된다.
   - 만약 'username' 값에 실제 사용자이름 대신 "admin ' or 1=1 -- " 라고 전달한다면, 최종적으로 실행되는 Query는 " SELECT * FROM users WHERE username = admin ' or 1=1 -- " 이 된다.   이렇게 되면 전형적인 SQL Injection 공격에 노출되는 상황이 발생되는 것이다.
   - 만약 위에서 처럼 $에 SQL Injection 을 위한 Parameter가 전달되면 username이 admin 이 아니더라도 TRUE가 된다.
   - 일반적으로 $ 기호는 Table 이름이나 Column 이름을 전달하는 용도로만 사용하는게 바람직하다.

 

#는 조건값으로, $는 Table이나 Column 값으로 사용

   예를들어서 살펴보자.   조회 대상 Table 과 Column 의 값이 유동적으로 바뀌어야 하는 경우(예를 들어 Partition Table을 사용하는 경우 직접 해당 Table을 지정해야 하는 경우)를 예로 살펴보자.

[ 지역별로 Table이 Partition으로 구분된 Table ]

   위의 경우 처럼 대표 Table이 있고 지역별로 Partition되어 있다고 했을때 전체를 대상으로 조회하는 것보다 특정 지역을 한정할 수 있다면 한정지어서 조회하는 것이 검색 속도를 확연하게 빠르게 하는 결과를 가져올 것이다.   Data가 많지 않다면 차이가 없겠지만 Data양이 많다면 그 차이는 더욱 확연해 진다.   어쨌뜬 저렇게 특정 테이블을 지정해서 보다 효과적인 결과를 낼 수 있는 사황이라고 가정한다면 테이블명을 유동적으로 사용하는게 유용할 것이다.

SELECT * FROM tb0000_zipcode_02 WHERE roadname ='논현로8길'

   위와 같은 Query를 생성하기 위해서 아래와 같이 사용하는 것이다.

SELECT * FROM tb0000_zipcode_${Param1} WHERE roadname = #{Param2}

   그리고 Parameter로 Param1의 값은 "02", Param2 값으로 "논현로8길"을 전달하면 된다.

 

   요약하자면 '#'은 SQL Injection을 방지하지만 '$'는 그렇지 않다.   일반적으로 '#'이 더 나은 성능을 제공한다.   그 이유는 Parameter를 미리 Compile 된 Query에 Binding하기 때문이다.   '#'과 '$'를 올바르게 사용하여 보안과 성능을 최적화 하는 것이 중요하다.   '#'를 사용하여 Binding하고 '$'는 신뢰할 수 있는 경우에만 사용해야 한다.