TIL/[TIL]

[TIL]MyBatis Mapper 방식, #{}와 ${}, 동적 SQL

namerong 2026. 6. 5. 23:30

1. 학습 주제

MyBatis에서 SQL을 실행하는 방식을 한 단계 더 발전시켰다.
단, 이번 TIL에서는 요청대로 api_practic 폴더 내용은 제외하고 정리한다.

  • #{}와 ${}의 차이
  • MyBatis SQL 작성 방식 2가지
  • XML + DAO 방식
  • 어노테이션 + Mapper 방식
  • Mapper 인터페이스 + XML 조합 방식
  • 동적 SQL에서 <if> 사용하기
  • 실무에서 선호되는 Mapper + XML 패턴 이해하기

2. #{}와 ${} 차이

MyBatis에서 값을 SQL에 넣을 때는 크게 두 방식이 있다.

#{}
→ PreparedStatement 방식

${}
→ Statement 방식

3. #{}

#{}는 PreparedStatement의 파라미터 바인딩 방식이다.

예:

WHERE menu_code = #{code}

MyBatis는 이 값을 SQL 문자열에 바로 붙이지 않고, ? 자리로 처리한 뒤 안전하게 값을 바인딩한다.

실제 흐름은 이런 느낌이다.

WHERE menu_code = ?

그리고 code 값이 나중에 들어간다.

장점:

  • SQL Injection에 비교적 안전하다.
  • 문자열에 따옴표를 직접 붙이지 않아도 된다.
  • 일반적인 값 전달에는 #{}를 사용한다.

대부분의 경우 #{}를 쓰는 것이 기본이다.


4. ${}

${}는 Statement처럼 문자열을 그대로 SQL에 끼워 넣는 방식이다.

예:

ORDER BY ${columnName}

값이 menu_price라면 SQL이 이렇게 만들어진다.

ORDER BY menu_price

즉 값이 SQL 문자열에 직접 합쳐진다.

주의할 점:

  • SQL Injection 위험이 있다.
  • 사용자 입력값을 그대로 넣으면 위험하다.
  • 컬럼명, 테이블명처럼 #{}로 바인딩할 수 없는 SQL 조각에 제한적으로 사용한다.

정리하면:

일반 값: #{}
SQL 문법 조각: ${}

5. MyBatis SQL 작성 방식 2가지

이번 범위에서는 MyBatis를 사용하는 방식이 크게 두 가지로 비교된다.

1. XML + DAO
2. 어노테이션 + Mapper

그리고 이후에는 이 둘을 섞은 실무형 패턴으로 발전한다.

Mapper 인터페이스 + XML SQL

6. XML + DAO 방식

기존 방식은 DAO에서 문자열로 Mapper SQL을 호출했다.

[MenuDAO.java]

public List<MenuDTO> selectAllMenu(SqlSession sqlSession){
    return sqlSession.selectList("MenuMapper.selectAllMenu");
}

흐름은 다음과 같다.

Service
↓
MenuDAO
↓
SqlSession.selectList("MenuMapper.selectAllMenu")
↓
menu-mapper.xml
↓
SQL 실행

이 방식은 SQL이 XML에 분리되어 있어 SQL 관리가 쉽다.
하지만 Java 코드에서 SQL id를 문자열로 호출하기 때문에 오타가 나도 컴파일 단계에서 잡기 어렵다.

예:

"MenuMapper.selectAllMenu"

이 문자열이 틀려도 실행 전까지 알기 어렵다.


7. 어노테이션 + Mapper 방식

[MenuMapper.java]

@Select("SELECT menu_code, menu_name, menu_price, category_code, orderable_status " +
        "FROM tbl_menu " +
        "WHERE orderable_status = 'Y' " +
        "ORDER BY menu_code")
List<MenuDTO> selectAllMenu();

이 방식은 Mapper 인터페이스의 메서드에 SQL을 직접 붙인다.

Service에서는 문자열 SQL id를 호출하지 않고 Mapper 인터페이스를 가져온다.

[MenuService.java]

MenuMapper menuMapper = sqlSession.getMapper(MenuMapper.class);

return menuMapper.selectAllMenu();

흐름은 다음과 같다.

Service
↓
SqlSession.getMapper(MenuMapper.class)
↓
MenuMapper.selectAllMenu()
↓
어노테이션 SQL 실행

장점은 문자열 호출보다 타입 안정성이 좋아진다는 점이다.
메서드 이름이 Java 코드로 존재하므로 오타를 컴파일 단계에서 어느 정도 잡을 수 있다.


8. Mapper 인터페이스 + XML 방식

[MenuMapper.java]

public interface MenuMapper {

    List<MenuDTO> selectAllMenu();

    MenuDTO selectMenuByCode(int code);

    int insertMenu(MenuDTO menu);

    int updateMenu(MenuDTO menu);

    int deleteMenu(int code);
}

SQL은 XML에 작성한다.

[MenuMapper.xml]

<mapper namespace="com.ohgiraffers.section03.remix.MenuMapper">

여기서 중요한 연결 규칙은 다음과 같다.

XML namespace
= Mapper 인터페이스의 풀 클래스명

XML id
= Mapper 인터페이스의 메서드명

예:

int updateMenu(MenuDTO menu);
<update id="updateMenu" parameterType="com.section03.remix.MenuDTO">
    UPDATE tbl_menu
    SET menu_name = #{name},
        menu_price = #{price},
        category_code = #{categoryCode}
    WHERE menu_code = #{code}
</update>

MyBatis가 인터페이스 메서드와 XML SQL을 연결해준다.


9. 호출 흐름의 변화

기존 방식은 문자열 기반이었다.

Service
↓
MenuDAO
↓
SqlSession 문자열 호출
↓
XML SQL 실행

예:

sqlSession.selectList("MenuMapper.selectAllMenu");

변경된 방식은 Mapper 인터페이스 기반이다.

Service
↓
MenuMapper 인터페이스
↓
XML SQL 실행

예:

MenuMapper menuMapper = sqlSession.getMapper(MenuMapper.class);
return menuMapper.selectAllMenu();

이 변경의 의미는 크다.

문자열 SQL id 직접 호출
→ Mapper 메서드 호출

즉 Java 코드 입장에서는 SQL을 문자열로 부르는 것이 아니라, 인터페이스 메서드를 호출하는 구조가 된다.


10. MyBatis 실무 표준 패턴

MyBatis 실무 표준 패턴은 다음이다.

Mapper 인터페이스 + XML SQL

장점만 정리하면:

Mapper 인터페이스
→ 타입 안정성
→ 메서드 기반 호출
→ 문자열 SQL id 오타 감소

XML
→ SQL 분리
→ 복잡한 SQL 관리에 유리
→ 동적 쿼리 가독성 좋음

즉 어노테이션 방식은 간단한 SQL에는 편하지만, SQL이 길어지고 조건이 많아지면 XML이 더 읽기 좋다.

그래서 실무에서는 보통:

Java에서는 Mapper 인터페이스 메서드 호출
SQL은 XML에 분리

하는 방식을 많이 사용한다.


11. 동적 SQL

조건에 따라 SQL 일부를 붙였다 뺐다 하는 동적 SQL을 학습했다.

[DynamicSqlMapper.xml]

<if test="price gte 0 and price lte 10000">
    <![CDATA[
    AND A.MENU_PRICE < #{ price }
    ]]>
</if>

<if>는 조건이 true일 때만 SQL 조각을 추가한다.

예를 들어 가격이 10000 이하이면 다음 조건이 붙는다.

AND A.MENU_PRICE < ?

가격 범위가 다르면 다른 조건이 붙는다.

<if test="price gt 10000 and price lte 20000">
    AND A.MENU_PRICE BETWEEN 10000 AND #{ price }
</if>

12. 검색 조건에 따른 SQL 변경

검색 조건이 name인지 category인지에 따라 SQL이 달라진다.

<if test="condition == 'category'">
    JOIN tbl_category B ON (A.CATEGORY_CODE = B.CATEGORY_CODE)
</if>

카테고리 검색일 때만 JOIN이 붙는다.

<if test="condition == 'name'">
    AND A.MENU_NAME LIKE CONCAT('%', #{ value }, '%')
</if>

이름 검색일 때는 메뉴명 LIKE 조건이 붙는다.

<if test="condition == 'category' ">
    AND B.CATEGORY_NAME = #{ value }
</if>

카테고리 검색일 때는 카테고리명 조건이 붙는다.

즉 같은 메서드라도 전달된 조건에 따라 다른 SQL이 만들어진다.


13. 동적 SQL에서 XML이 유리한 이유

동적 SQL은 조건이 많아질수록 어노테이션보다 XML이 읽기 쉽다.

예를 들어 다음 기능들은 XML에서 더 관리하기 좋다.

<if>
<choose>
<when>
<otherwise>
<foreach>
<where>
<trim>
<set>

이번 코드에는 일부 TODO도 남아 있지만, 학습 방향은 명확하다.

조건에 따라 WHERE 절 추가
조건에 따라 JOIN 추가
반복문으로 IN 절 생성
입력된 값만 SET 절에 포함

이런 SQL은 Java 어노테이션 안에 문자열로 작성하면 금방 복잡해진다.
그래서 Mapper 인터페이스 + XML 조합이 실무에서 많이 쓰인다.


14. typeAlias

[mybatis-config.xml]에는 typeAlias 설정이 있다.

<typeAliases>
    <typeAlias type="com.ohgiraffers.section01.SearchCriteria" alias="SearchCriteria"/>
    <typeAlias type="com.ohgiraffers.section01.MenuDTO" alias="MenuDTO"/>
</typeAliases>

이렇게 하면 XML에서 긴 클래스명을 매번 쓰지 않아도 된다.

parameterType="SearchCriteria"
resultMap="menuResultMap"

MenuDTO, SearchCriteria 같은 짧은 이름으로 사용할 수 있어 XML이 더 깔끔해진다.


15. properties 설정 분리

DB 연결 정보도 별도 파일로 분리했다.

[connection-info.properties]

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost/menudb
username=ohgiraffers
password=ohgiraffers

그리고 MyBatis 설정에서 가져온다.

<properties resource="config/connection-info.properties"/>
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>

여기서 ${driver} 같은 표현은 SQL 파라미터가 아니라 설정값 치환이다.
SQL에서 사용하는 ${}와는 맥락이 다르지만, 문자열 치환이라는 점은 비슷하다.


16. 핵심 정리

  1. #{}는 PreparedStatement 방식으로 값을 안전하게 바인딩한다.
  2. ${}는 Statement 방식으로 문자열을 SQL에 직접 치환한다.
  3. 일반 값 전달에는 대부분 #{}를 사용한다.
  4. ${}는 SQL Injection 위험이 있으므로 제한적으로 사용해야 한다.
  5. MyBatis 사용 방식에는 XML + DAO 방식과 어노테이션 + Mapper 방식이 있다.
  6. XML + DAO 방식은 SQL 분리는 좋지만 문자열 호출이라 타입 안정성이 약하다.
  7. 어노테이션 + Mapper 방식은 간단한 SQL에 편하고 Mapper 메서드 호출이 가능하다.
  8. 실무에서는 Mapper 인터페이스 + XML SQL 조합이 많이 사용된다.
  9. Mapper 인터페이스는 타입 안정성을 제공한다.
  10. XML은 SQL 분리와 동적 쿼리 가독성에 유리하다.
  11. Service → MenuDAO → SqlSession 문자열 호출 구조가 Service → MenuMapper 인터페이스 → XML SQL 실행 구조로 변경되었다.
  12. 동적 SQL은 조건에 따라 SQL 조각을 추가하거나 생략하는 기능이다.
  13. <if>는 조건이 참일 때만 SQL 일부를 붙인다.
  14. typeAlias를 사용하면 XML에서 긴 클래스명을 짧게 사용할 수 있다.
  15. DB 연결 정보는 properties 파일로 분리해 관리할 수 있다.