TIL/[TIL]

[TIL]MyBatis 동적 쿼리와 Spring MyBatis 기본 흐름

namerong 2026. 6. 8. 20:23

1. 학습 주제

  • MyBatis의 동적 쿼리
  • Spring Boot와 MyBatis를 연결해 조회 API를 만드는 흐름

즉 단순히 SQL을 고정해서 실행하는 단계에서 나아가,
조건에 따라 SQL이 달라지는 구조
Controller -> Service -> Mapper -> XML SQL 흐름으로 데이터를 조회하는 구조를 함께 학습한 셈이다.

이번 범위는 특히 “조건이 있을 때만 쿼리를 붙인다”, “반복문처럼 IN 조건을 만든다”, “Spring에서 Mapper를 어떻게 연결하는가”를 이해하는 데 의미가 있었다.


2. MyBatis 동적 쿼리란 무엇인가

동적 쿼리는 말 그대로 조건에 따라 SQL이 달라지는 방식이다.

보통 고정 SQL은 항상 같은 형태로 실행된다.

예:

  • 메뉴 전체 조회
  • 특정 ID 한 건 조회

하지만 실제 서비스에서는 조건이 자주 달라진다.

예:

  • 가격이 10000원 이하일 때만 검색
  • 이름으로 검색할 수도 있고 카테고리로 검색할 수도 있음
  • 검색 조건이 없으면 전체 조회
  • 수정할 때 입력한 값만 바꾸고 싶음

이런 상황에서는 SQL을 하나로 고정하기 어렵다.
그래서 MyBatis는 XML 안에서 조건문처럼 동작하는 태그를 제공한다.

대표적으로 이번 범위에서 다룬 것은:

  • <if>
  • <choose>, <when>, <otherwise>
  • <foreach>
  • <where>
  • <trim>

이다.

즉 동적 쿼리는
Java에서 if문을 쓰듯이 SQL도 조건에 따라 조립하는 방식이라고 이해하면 된다.


3. <if> 태그

가장 먼저 확인한 것은 <if>였다.

이 태그는 특정 조건이 참일 때만 SQL 조각을 붙인다.

예를 들면 가격 조건 검색에서:

  • price가 어떤 범위일 때
  • 다른 WHERE 조건을 붙이는 방식

으로 사용했다.

핵심은 다음과 같다.

  • 조건이 참이면 SQL이 들어간다
  • 조건이 거짓이면 그 부분은 빠진다

즉 <if>는
가장 기본적인 동적 조건 처리 태그이다.

이걸 통해 배운 중요한 감각은,
SQL도 “완성된 문자열”이 아니라 “조건에 따라 조립되는 구조”라는 점이다.


4. 검색 기준에 따라 JOIN 자체를 다르게 붙이는 방식

이번 범위에서는 단순히 WHERE 조건만 붙이는 게 아니라,
필요할 때만 JOIN을 추가하는 구조도 나왔다.

예를 들어:

  • 이름으로 검색할 때는 메뉴 테이블만 보면 되고
  • 카테고리명으로 검색할 때는 카테고리 테이블 JOIN이 필요하다

이럴 때 <if>로 JOIN 구문 자체를 조건부로 넣었다.

이게 중요한 이유는,
동적 쿼리는 단순히 “값 비교 조건”만 바꾸는 게 아니라
FROM/JOIN 구조까지 조건에 따라 달라질 수 있다는 점을 보여주기 때문이다.


5. <choose>, <when>, <otherwise>

이 부분은 Java의 switch문과 비슷하게 이해하면 쉽다.

예를 들어 상위 분류가:

  • 식사
  • 음료
  • 그 외

중 무엇인지에 따라 다른 카테고리 조건을 붙이는 방식이었다.

즉 여러 조건 중 하나만 선택해서 SQL에 반영하고 싶을 때 사용한다.

구조적으로는:

  • <when>: 조건이 맞을 때
  • <otherwise>: 어느 것도 안 맞을 때 기본값

이다.

이 태그가 중요한 이유는,
서로 배타적인 조건을 다룰 때 <if>를 여러 개 쓰는 것보다
의도를 더 분명하게 표현할 수 있기 때문이다.

즉 <choose>는
“여러 후보 중 하나만 골라야 할 때” 쓰는 동적 쿼리 태그라고 정리할 수 있다.


6. <foreach>

<foreach>는 반복문처럼 동작하는 태그다.

이번 예제에서는 랜덤한 메뉴 코드 5개를 뽑고,
그 번호들을 IN (...) 조건으로 조회하는 구조가 나왔다.

즉 Java 쪽에서 만든 리스트를 Mapper로 넘기고,
XML에서는 <foreach>로 이를 순회해서 SQL을 만든다.

예를 들면 개념적으로 이런 식이다.

  • 리스트: [3, 7, 12, 18, 25]
  • SQL: IN (3, 7, 12, 18, 25)

이 태그에서 중요한 속성은 다음이다.

  • collection: 반복할 대상
  • item: 하나씩 꺼낼 값 이름
  • open: 앞에 붙일 문자
  • separator: 값 사이 구분자
  • close: 뒤에 붙일 문자

즉 <foreach>는
리스트나 배열 같은 여러 값을 SQL 안에 펼쳐 넣는 데 매우 유용한 태그다.

실무에서도 IN 검색 조건에서 굉장히 자주 쓰인다.


7. <where>

<where>는 동적 조건이 많아질 때 특히 편한 태그다.

문제 상황은 보통 이렇다.

  • 조건이 있으면 WHERE 뒤에 붙이고 싶은데
  • 조건이 하나도 없으면 WHERE가 없어야 한다
  • 조건이 AND로 시작하면 문법이 이상해질 수 있다

<where>는 이런 문제를 해결해준다.

핵심 기능:

  • 내부에 실제 조건이 있으면 WHERE를 자동으로 붙여준다
  • 맨 앞이 AND나 OR이면 자동으로 제거해준다
  • 내부 조건이 하나도 없으면 WHERE 자체를 만들지 않는다

즉 <where>는
조건 유무에 따라 WHERE 절을 안전하게 만들어주는 태그다.

이번 학습에서는:

  • 기본 판매 가능 조건을 두고
  • 메뉴 코드 조건이 있을 때만 추가하는 방식

으로 사용했다.


8. <trim>

<trim>은 <where>나 <set>보다 조금 더 범용적인 태그다.

즉 “앞에 어떤 문자를 붙일지”, “앞이나 뒤에서 어떤 문자를 제거할지”를 더 직접 제어할 수 있다.

이번 범위에서 의도한 쓰임은 두 가지였다.

1. 조회 조건 묶기

이름 조건이나 카테고리 조건이 있을 때만 추가

2. 수정 쿼리의 SET 절 만들기

입력된 값만 골라서 SET에 포함

즉 <trim>은
SQL 조각의 앞뒤 문법을 정리해주는 태그라고 보면 된다.

<where>와 <set>이 좀 더 특화된 버전이라면,
<trim>은 직접 규칙을 지정하는 조금 더 일반적인 도구에 가깝다.


9. 수정 쿼리에서 동적 SET이 중요한 이유

수정 기능에서는 사용자가 모든 값을 다 바꾸는 게 아니라,
일부만 바꾸는 경우가 많다.

예:

  • 이름만 수정
  • 카테고리만 수정
  • 판매 여부만 수정

이때 모든 컬럼을 무조건 업데이트하면 불편하다.
그래서 입력된 값만 골라서 SET 절에 넣는 방식이 필요하다.

이런 상황에서 <trim prefix="SET"> 같은 구조를 사용하면:

  • 실제로 값이 들어온 항목만 SET에 포함
  • 맨 앞의 불필요한 쉼표 정리
  • 문법적으로 안전한 UPDATE 쿼리 구성

이 가능하다.

즉 동적 쿼리는 조회뿐 아니라
부분 수정(partial update) 에서도 매우 중요하다.


10. Java 쪽에서 조건을 어떻게 넘기는가

동적 쿼리는 XML만 중요한 게 아니다.
Java 쪽에서 어떤 자료구조로 값을 넘기는지도 중요하다.

이번 범위에서는 주로 두 가지 방식이 나왔다.

1. DTO나 조건 객체

예: SearchCriteria

  • condition
  • value

같은 검색 기준을 하나의 객체로 묶어서 전달

2. Map<String, Object>

조건이 가변적일 때 사용

예:

  • 이름 조건이 있을 수도 있고
  • 카테고리 조건이 있을 수도 있고
  • 둘 다 있을 수도 있음

이럴 때는 Map이 유연하다.

즉 Java 쪽에서는
정적인 구조는 DTO,
유동적인 조건은 Map
으로 넘기는 감각을 익힐 수 있었다.


11. Spring MyBatis 구조

다음으로는 Spring Boot에서 MyBatis를 연결하는 흐름이었다.

전체 구조는 다음과 같다.

  • Controller
  • Service
  • Mapper 인터페이스
  • XML Mapper

이 구조를 따라가면:

  1. Controller가 요청을 받는다
  2. Service가 비즈니스 흐름을 맡는다
  3. Mapper 인터페이스가 SQL 호출 창구 역할을 한다
  4. 실제 SQL은 XML에 작성된다

즉 Spring MyBatis는
자바 메서드 호출처럼 보이지만, 실제로는 XML에 적힌 SQL이 실행되는 구조다.


12. @Mapper의 의미

Mapper 인터페이스에는 @Mapper가 붙어 있었다.

이 어노테이션의 의미는:

  • 이 인터페이스는 MyBatis Mapper이다
  • Spring이 Bean으로 등록하고
  • SQL과 연결해서 사용할 수 있게 해준다

즉 개발자는 인터페이스만 정의하고,
실제 구현체는 MyBatis가 동적으로 만들어주는 구조다.

이 점이 JDBC와 비교했을 때 큰 차이이다.

JDBC에서는:

  • Connection 열고
  • PreparedStatement 만들고
  • ResultSet 읽는 코드

를 직접 작성했다면,

MyBatis에서는:

  • 메서드 선언
  • XML SQL 작성

으로 훨씬 추상화된다.


13. Controller -> Service -> Mapper 흐름

이번 예제는 이 계층 구조를 간단하게 보여준다.

Controller

  • HTTP 요청 받기
  • 응답 형태 결정
  • ResponseEntity 반환

Service

  • 컨트롤러와 매퍼 사이 연결
  • 필요한 비즈니스 흐름 처리

Mapper

  • 실제 SQL 호출

예를 들어 메뉴 전체 조회 요청은:

  1. 클라이언트가 GET /menus 요청
  2. Controller가 findAllMenu() 호출
  3. Service가 menuMapper.findAllMenu() 호출
  4. XML의 findAllMenu SQL 실행
  5. 결과를 List<MenuDTO>로 받아 응답

이렇게 흐른다.

즉 웹 요청이 DB 조회까지 이어지는 전체 흐름을 아주 기본 형태로 본 셈이다.


14. 메뉴 전체 조회

가장 기본적인 기능은 전체 메뉴 조회였다.

Mapper XML에는:

  • 판매 가능 상태인 메뉴만 조회
  • 메뉴 코드 순으로 정렬

하는 쿼리가 있었다.

이 쿼리를 통해 알 수 있는 점은:

  • MyBatis는 결과 컬럼을 DTO 필드에 매핑할 수 있고
  • 그 매핑을 resultMap으로 명시할 수 있다는 점이다.

즉 DB 컬럼명과 자바 필드명이 완전히 같지 않아도
매핑 규칙을 통해 객체로 바꿀 수 있다.


15. resultMap의 역할

resultMap은 매우 중요하다.

예를 들면 DB는:

  • menu_code
  • menu_name

처럼 스네이크 케이스를 쓰고,

Java DTO는:

  • code
  • name

처럼 다른 이름을 가질 수 있다.

이럴 때 resultMap이:

  • 어떤 컬럼을 어떤 필드에 넣을지

정해준다.

즉 resultMap은
SQL 결과를 자바 객체로 바꾸는 매핑 규칙이라고 이해하면 된다.


16. 단건 조회와 @PathVariable

컨트롤러에서는:

  • URL 경로에서 menuCode를 받고
  • 서비스에 전달한 뒤
  • 해당 메뉴가 없으면 404 Not Found
  • 있으면 200 OK

를 반환한다.

이 흐름이 중요한 이유는,
단순 조회를 넘어서 REST API스럽게 응답 상태를 표현하는 방식을 익혔기 때문이다.

즉:

  • 데이터 있으면 ok
  • 없으면 notFound

라는 응답 구조를 직접 다루기 시작한 것이다.


17. @Param을 왜 쓰는가

Mapper 인터페이스의 단건 조회 메서드에는 @Param("menuCode")가 붙어 있었다.

이 어노테이션은:

  • Java 메서드 파라미터 이름을
  • XML 쿼리 안에서 사용할 이름으로 명확히 지정해준다

즉 XML에서는 #{menuCode}처럼 참조할 수 있게 된다.

파라미터가 하나일 때도 쓸 수 있고,
여러 개일 때는 특히 더 중요하다.

즉 @Param은
Java 메서드 인자와 XML SQL 파라미터 이름을 연결하는 도구라고 보면 된다.


18. 설정 파일에서 본 MyBatis 연결

설정 파일에서는 다음 흐름이 보였다.

  • 데이터베이스 접속 정보
  • MyBatis mapper XML 위치

즉 Spring Boot는:

  • datasource 설정으로 DB 연결을 만들고
  • mapper-locations 설정으로 XML Mapper 파일을 읽어
  • Mapper 인터페이스와 연결한다

이 구조를 이해하면,
Spring Boot + MyBatis가 결국 DB 연결 + SQL 파일 매핑으로 이루어진다는 점이 보인다.


19. 동적 쿼리와 Spring MyBatis를 함께 보면 좋은 이유

이번 범위는 두 파트가 따로 보일 수 있지만 사실 연결된다.

동적 쿼리

조건에 따라 SQL이 달라진다

Spring MyBatis

그 SQL을 웹 요청 흐름 안에서 실행한다

즉 나중에는 이런 구조가 자연스럽게 이어진다.

  1. 클라이언트가 검색 조건을 보낸다
  2. Controller가 조건을 받는다
  3. Service가 Mapper로 넘긴다
  4. Mapper XML이 동적 쿼리로 SQL을 조립한다
  5. DB 결과를 DTO로 반환한다

즉 동적 쿼리는 “SQL 작성 기술”이고,
Spring MyBatis는 “그 SQL을 웹 애플리케이션 안에서 쓰는 구조”라고 보면 된다.


20. 학습 흐름 정리

20.1 동적 쿼리

  • <if>로 조건부 WHERE/JOIN 추가
  • <choose>로 여러 조건 중 하나 선택
  • <foreach>로 IN 조건 동적 생성
  • <where>로 WHERE 절 자동 정리
  • <trim>으로 조건절과 SET 절 유연하게 구성
  • Map과 조건 객체를 활용해 동적 파라미터 전달

20.2 Spring MyBatis

  • Controller -> Service -> Mapper 구조 이해
  • @Mapper 인터페이스와 XML SQL 연결
  • resultMap으로 컬럼과 DTO 필드 매핑
  • 전체 조회 API와 단건 조회 API 구현 흐름 확인
  • @Param으로 파라미터 이름 명시

21. 핵심 정리

  1. MyBatis 동적 쿼리는 조건에 따라 SQL을 유연하게 조립하는 기능이다.
  2. <if>는 조건이 참일 때만 특정 SQL 조각을 포함한다.
  3. <choose>, <when>, <otherwise>는 여러 조건 중 하나만 선택할 때 사용한다.
  4. <foreach>는 리스트나 배열을 반복해 IN (...) 조건 같은 SQL을 만들 때 유용하다.
  5. <where>는 WHERE 절을 자동으로 정리해 주기 때문에 동적 조건 처리에서 매우 편하다.
  6. <trim>은 SQL 조각의 앞뒤 문법을 더 세밀하게 제어할 수 있다.
  7. 부분 수정처럼 입력된 값만 반영해야 할 때 동적 SET 구성이 중요하다.
  8. Spring MyBatis는 Controller -> Service -> Mapper -> XML SQL 흐름으로 동작한다.
  9. @Mapper는 MyBatis 매퍼 인터페이스를 Spring이 관리할 수 있게 해준다.
  10. resultMap은 DB 컬럼과 자바 DTO 필드를 연결하는 매핑 규칙이다.
  11. @Param은 Java 메서드 파라미터와 XML SQL 파라미터 이름을 명확히 연결해준다.
  12. 이번 범위의 핵심은 “조건에 따라 SQL을 바꾸는 법”과 “그 SQL을 Spring 웹 흐름 안에서 호출하는 법”을 함께 이해하는 것이다.