[Mybatis] ${}과 #{}의 차이

yourjin·2023년 5월 11일
1

dev.log

목록 보기
11/14

➕ Topic


  • Mybatis를 사용하다 보면, xml에서 파라미터를 매핑할 때 #{}${}를 사용하게 된다.
  • 이번에 소스 코드 취약점 진단을 받고 ${} 로 되어있던 부분을 #{} 로 수정했는데, 이때 알게 된 차이점과 각각 언제 사용하면 좋은지 정리해보았다.

➕ Contents


Mybatis - $, #

Mybatis의 $, #는 사실 Java의 Statement, PreparedStatement의 차이라고도 볼 수 있다.

Mybatis 3 공식 문서에 나와 있는 설명을 보자면 다음과 같다.

Notice the parameter notation:

#{id}

This tells MyBatis to create a PreparedStatement parameter. With JDBC, such a parameter would be identified by a "?" in SQL passed to a new PreparedStatement, something like this:

// Similar JDBC code, NOT MyBatis…
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1,id);

해석해보면 #{} 를 사용할 때는 Mybatis에서 PreparedStatement를 만들어서 파라미터를 바인딩한다고 한다. 여기서 파라미터들은 SQL 문에서 ? 으로 표시되며, 구문 분석을 할 때 하나의 필드 값으로 인식한다고 한다. 쉽게 말하자면 파라미터 값을 구문의 일부가 아닌, 따옴표로 묶은 하나의 값(value)으로 인식한다는 것이다.

String Substitution

By default, using the #{} syntax will cause MyBatis to generate PreparedStatement properties and set the values safely against the PreparedStatement parameters (e.g. ?). While this is safer, faster and almost always preferred, sometimes you just want to directly inject an unmodified string into the SQL Statement. For example, for ORDER BY, you might use something like this:

ORDER BY ${columnName}

Here MyBatis won't modify or escape the string.

String Substitution(문자열 치환)이라는 이름에서 유추할 수 있듯이, ${} 를 사용하면 어떠한 조작도 없이 문자열 그대로 SQL 문에 들어간다. 보통 컬럼명과 같이 값(value)로 들어갈 수 없는 곳에 변수를 쓰고 싶을 때 사용한다고 한다.

$를 사용하는 경우, #을 사용하는 경우

쿼리문을 만들어서 실행하는 것은 $나 # 중 아무거나 사용해도 크게 상관이 없다. 다만 #{} 을 사용하면 PreparedStatement로 작동하기 때문에 SQL Injection 등의 보안 문제를 예방할 수 있어 일반적으로 더 권장된다. 그리고 Prepared + Statement 라는 이름에 걸맞게 객체를 캐시에 담아 재사용하기 때문에 반복적인 쿼리에서 Statement보다 성능이 좋다.

그렇다면 전부 #{} 를 사용하면 될 것 같지만, 간혹 ${}를 사용해야 하는 경우도 존재한다. 앞에서도 잠깐 언급했듯이 값이 아닌 컬럼명과 같이 구문(Statement)의 일부가 변수 처리되어야 하는 경우가 그렇다. 이런 경우는 외부 값이 아닌 안전한 내부(프로그램의 내부) 값을 사용하고 있는 지 확인하는 게 좋다.

그 밖에도 PreparedStatement를 사용했을 때 성능에 이슈를 주는 경우가 있다. 예를 들면 MSSQL에서 java의 String 객체를 사용할 때, 컬럼 타입이 NVARCHAR이면 타입이 같아서 상관 없는데 VARCHAR이면 타입 변환이 일어나서 풀스캔을 한다고 한다. 나의 경우 취약점 진단을 받고 #으로 변경했다가 쿼리 성능이 현저히 떨어져서 다시 $로 변경하였다. 오라클은 변수 앞에 :(콜론)을 붙히면 PreparedStatement로 동작한다고 하는데, 이런 식으로 속도 체크를 해보는 것도 좋을 것 같다.

JDBC - Statement, PreparedStatement

JDBC를 직접 사용하는 드물어서 모르는 부분이 많은 것 같아 Statement과 PreparedStatement에 대한 내용도 잠깐 짚고 넘어가려고 한다.

JDBC(Java Database Connectivity) 는 자바에서 데이터베이스와 연결하여 데이터를 주고 받을 수 있도록 만든 인터페이스이다. 이 인터페이스의 구현 클래스(jar)를 JDBC 드라이버라고 한다. JDBC 드라이버는 연결하는 DBMS의 종류에 따라서 별도로 구현되어 있다. DBMS와 연결하기 JDBC URL도 필요한데, 어떤 JDBC 드라이버를 쓰느냐에 따라서 형식이 달라진다. 보통 DB 접속을 위해 아래와 같이 접속 정보를 설정 파일에 저장해서 사용한다.

# Oracle Connection Information
UserName=계정명
Password=계정비밀번호
DriverClassName=oracle.jdbc.driver.OracleDriver
Url=jdbc:oracle:thin:@HOST:PORT:SID

JDBC에서 제공하는 인터페이스 중에 Connection, Statement, ResultSet 이 있다. Connection은 데이터베이스 연결, Statement는 SQL문 생성 및 실행, ResultSet은 SQL문 실행 결과를 받는 역할을 한다. Statement에서 executeQuery() 라는 메소드가 SQL문의 실행(SELECT)을 담당하는데, PreparedStatement는 Statement를 상속 받기 때문에 똑같이 executeQuery() 메소드가 존재한다. 하지만 내부 구현에서 캐시 사용 여부 등의 차이가 있기 때문에, 같은 이름의 메소드를 호출해도 실제 결과/성능은 다르게 나올 수 있다.

➕ References


profile
make it mine, make it yours

0개의 댓글