[MyBatis] 동적 SQL

컨테이너·2025년 11월 16일
0

SpringFramework

목록 보기
4/15
post-thumbnail

실습 암기노트

chap03-dynamic-sql-lecture


1. Dynamic Query

Dynamic Query

  • 상황(조건)에 따라 SQL 조각을 붙였다가 떼었다가 하는 기능이라고 보면 된다.
  • DAO 메서드를 여러개 만들지 않고, 한 SQL을 조건에 따라 재사용하기 위함이다.

XML에서 지원하는 동적 태그

1. if
2. choose / when / otherwise
3. trim / where / set
4. foreach
5. bind

2. XML Dynamic Query 태그 정리

2-1. if

역할

  • test 조건이 참일 때만 해당 SQL 구문 포함

형태

<if test="title != null">
    AND title LIKE #{title}
</if>

포인트

  • 조건 여러 개면 if 여러 개 사용 가능
  • 파라미터가 기본형이면 Map에 넣어서 키 이름으로 비교하는 패턴을 많이 씀
Map<String, Integer> map = new HashMap<>();
map.put("price", price):

2-2. choose/when/otherwise

역할

  • 여러 조건 중에서 한 가지만 골라야 하는 상황에 사용

형태

<choose>
    <when test="title != null">
        AND title LIKE #{title}
    </when>
    <when test="writer != null">
        AND writer LIKE #{writer}
    </when>
    <otherwise>
        AND featured = 1
    </otherwise>
</choose>

포인트

  • 여러 조건 중 우선순위 1개만 적용할 때 사용
  • if여러개 사용 시 참일 경우 모두 적용되지만 choose는 하나만 적용된다.

2-3. trim / where / set

공통 역할

  • 쿼리 앞/뒤에 붙는 WHERE, AND, , 같은 것을 자동으로 정리

trim

역할

  • 앞뒤에 특정 단어를 붙이거나 지워서 SQL을 깔끔하게 만들기

형태

<trim prefix="WHERE" prefixOverrides="AND |OR ">
    <if test="writer != null">
        writer = #{writer}
    </if>
    <if test="title != null">
        AND title LIKE #{title}
    </if>
</trim>

포인트

  • 내용이 하나라도 있으면(존재하면) prefix 붙임 (예: WHERE)
  • 내용이 AND, OR로 시작하면 prefixOverrides로 그걸 지우고 붙임
    ⇒ 만약 제일 앞 단어가 AND 나 OR 이면 삭제한다.

where

역할

  • 동적 WHERE 절 전용 태그 (trim의 축약 버전)

형태

<where>
    <if test="writer != null">
        writer = #{writer}
    </if>
    <if test="title != null">
        AND title LIKE #{title}
    </if>
</where>

포인트

  • 내부 내용이 있으면 WHERE 자동 추가
  • AND / OR 로 시작하면 앞 부분 자동 제거
  • 아무 조건도 없으면 WHERE 자체를 빼줌

set

문제상황

  • Set구문이 남거나, , 가 앞뒤로 붙어 구문오류가 발생할 수 있다.
<update id="updateMenu">
	UPDATE tbl_menu
		<if test="menuName != null and menuName != ''">
				SET menu_name = #{ menuName },
		</if>
		<if test="categoryCode != null and categoryCode gt 0">
				category_code = #{ categoryCode }
		</if>
		.
만약 첫번째 <if> 가 조건불충족으로 넘어가면, 'SET' 구문이 들어가지 않아 오류가 발생.

역할

  • 동적 UPDATE의 SET 절 전용 태그

형태

<update id="updateUser">
    UPDATE USER
    <set>
        <if test="username != null">
            username = #{username},
        </if>
        <if test="password != null">
            password = #{password},
        </if>
    </set>
    WHERE id = #{id}
</update>

포인트

  • 앞에 SET 자동 붙임 : <set> 을 사용 안하면 <if> 조건에 따라 SET,이 남거나 없어져서 구문오류가 발생할 수 있다.
  • 마지막 , 자동 제거
  • 일부 필드만 업데이트할 때 유용

2-4. foreach

역할

  • 컬렉션(List, 배열 등)에 들어있는 값들로 반복해서 SQL 조각 생성

주요 속성

  • collection : 반복 대상 (예: list, ids 등)
  • item : 반복마다 꺼내 쓸 변수 이름
  • index : 인덱스 (옵션)
  • open : 처음에 붙일 문자 (예: ()
  • separator : 사이사이에 넣을 구분자 (예: ,)
  • close : 마지막에 붙일 문자 (예: ))

형태

WHERE TITLE IN
<foreach item="item" collection="list"
         open="(" separator="," close=")">
    #{item}
</foreach>

결과 예

WHERE TITLE IN ('XXX', '사행성', '욕설', ...)

포인트

  • IN 절, 다중 삭제, 다중 조회 등에 자주 사용
  • collection에 static 값도 넣을 수 있음 (@풀클래스명@필드명, @풀클래스명@메소드명())

2-5. bind

역할

  • SQL에서 쓸 변수를 미리 가공해 두는 태그

형태

<bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />

SELECT * FROM BOARD
WHERE title LIKE #{pattern}

포인트

  • _parameter로 넘어온 객체에 접근
  • 복잡한 문자열 패턴 등을 미리 만들어 둘 때 사용

3. Java Provider (Annotation 방식 Dynamic Query)

3-1. Java Provider 개념

  • 동적 SQL을 XML 대신 Java 코드로 만드는 방식
  • Mapper 인터페이스 메서드에 @SelectProvider, @InsertProvider 등을 붙여 사용
  • Provider 클래스 안에서 SQL 객체를 이용해 쿼리 조립

3-2. @SelectProvider / @InsertProvider / @UpdateProvider / @DeleteProvider

역할

  • “이 메서드는 Provider 클래스의 어느 메서드가 만든 SQL을 실행해라” 라고 지정

형태

@ResultMap("menuResultMap")
@SelectProvider(type = SelectBuilderProvider.class,
                method = "searchMenuByCondition")
List<MenuDTO> searchMenuByCondition(SearchCriteria searchCriteria);

포인트

  • type: Provider 클래스 타입
  • method: Provider 클래스 안의 메서드 이름
  • 파라미터로 DTO나 Map을 넘길 수 있음

3-3. @Param

역할

  • Provider나 SQL에서 사용할 파라미터 이름을 지정

형태

@DeleteProvider(type = SqlBuilderProvider.class, method = "deleteMenu")
int deleteMenu(@Param("code") int code);

포인트

  • @Param("code") 로 이름을 붙이면 Provider에서 #{code}로 접근 가능
  • @Param을 쓰는 경우 Provider 메서드 매개변수는 비워두는 패턴을 사용하기도 함

3-3.5 SelectBuilder()

  • 조회용 builder이다.
  SQL sql = new SQL();
  sql.SELECT("menu_code", "menu_name", "menu_price", "category_code", "orderable_status")
          .FROM("tbl_menu");

  if("category".equals(searchCriteria.getCondition())) {
      sql.JOIN("tbl_category USING (category_code)")
              .WHERE("orderable_status = 'Y'")
              .AND()
              .WHERE("category_name = #{ value }");
  } else if ("name".equals(searchCriteria.getCondition())) {
      sql.WHERE(
              "orderable_status = 'Y'",
              "menu_name LIKE CONCAT('%', #{ value }, '%')"
      );
  }

  return sql.toString();
 
  • 위 두 개의WHERE구절을 살펴보면, AND() 가 사이에 있든, 없든 동일한 역할을 수행한다. 자동 AND() 처리된다.
  • Where 메소드에 가변인자로 전달하는 경우 자동적으로 AND조건 처리되므로 OR의 경우 별도 메소드를 사용한다.

출력

chap03-dynamic : section02
.
.
SqlSession sqlSession = getSqlsession();
SelectBuilderMapper mapper = sqlSession.getMapper(SelectBuilderMapper.class);
List<MenuDTO> menuList = mapper.selectAllMenu(); //받아오는 곳

if(menuList != null && !menuList.isEmpty()){
menuList.forEach(System.out :: println);
} else {
System.out.println("검색 결과가 존재하지 않습니다.");

3-4. Provider 클래스 · SQL builder

역할

  • org.apache.ibatis.jdbc.SQL 객체를 사용해 체이닝 방식으로 SQL을 조립하고 toString()으로 문자열 반환
  • 등록, 수정 삭제를 목적으로 두고 있음

기본 패턴

public String searchMenuByCondition(SearchCriteria searchCriteria) {

    SQL sql = new SQL();

    sql.SELECT("A.MENU_CODE")
       .SELECT("A.MENU_NAME")
       .FROM("TBL_MENU A");

    if ("category".equals(searchCriteria.getCondition())) {
        sql.JOIN("TBL_CATEGORY B ON (A.CATEGORY_CODE = B.CATEGORY_CODE)")
           .WHERE("A.ORDERABLE_STATUS = 'Y'")
           .AND()
           .WHERE("B.CATEGORY_NAME = #{value}");
    } else if ("name".equals(searchCriteria.getCondition())) {
        sql.WHERE("A.ORDERABLE_STATUS = 'Y'",
                  "A.MENU_NAME LIKE CONCAT('%', #{value}, '%')");
    }

    return sql.toString();
}

포인트

  • SELECT, FROM, JOIN, WHERE, AND, OR, INSERT_INTO, VALUES, UPDATE, SET, DELETE_FROM 등 메서드 제공
  • WHERE 안에 여러 문자열을 넣으면 기본 AND로 묶인다
  • 조건에 따라 if로 SELECT/WHERE/SET 조합을 바꾸면서 동적 쿼리 구현

InsertProvider

@InsertProvider(type=SqlBuilderProvider.class, method="insertMenu")
int insertMenu(SqlBuilderProvider

@Provider 어노테이션작성

@InsertProvider(type = SqlBuilderProvider.class, method = "insertMenu") no usages
int insertMenu(MenuDTO menuDTO);

/* 전달 파라미터가 유효한 값(문자열의 경우 빈 문자열이 아니여야 하고 숫자의 경우 0이상_을 가질
* 경우에만 수정에 반영하는 동적쿼리*/

@UpdateProvider(type= SqlBuilderProvider.class, method= "updateMenu") no usages new *
int updateMenu(MenuDTO menuDTO);

/* 기본 자료형 값을 전달하는 경우 @Param 어노테이션을 이용해야 한다.
* 또는 전달 값이 2개 이상인 경우에도 @Param어노테이션을 사용해야 한다.
* 단, Provider 메소드의 매개변수 선언부는 없애야 한다.
* */
@DeleteProvider(type= SqlBuilderProvider.class, method= "deleteMenu") no usages new x
int deleteMenu(@Param("menuCode") int menuCode);

& macacolabs
  • 매퍼 인터페이스의 메소드에 어노테이션을 작성해야 한다.
  • 어노테이션은 수행할 쿼리문에 따라 @SelectProvider, @InsertProvider, @UpdateProvider, @DeleteProvider로 작성한다.
  • 어노테이션의 type 속성에는 Provider의 클래스 타입을, method에는 수행할 쿼리ID를 적는다.
    • 사용 예시
      @ResultMap("menuResultMap")
      @SelectProvider(type=SelectBuilderProvider.class, method="searchMenuByCondition")
      List<MenuDTO> searchMenuByCondition(SearchCriteria searchCriteria);
  • 파라미터 타입이 Map이나 getter가 있는 DTO가 아닌 기본 자료형 값인 경우, 앞에 @Param 어노테이션을 이용할 수 있다.
    • 사용 예시
      @DeleteProvider(type=SqlBuilderProvider.class, method="deleteMenu")
      int deleteMenu(@Param("code") int code);
    • 단, param 어노테이션을 사용했다면 Provider 메소드의 매개변수 선언부는 비어있어야 한다.
      public String deleteMenu() {
      		*// 내용*
          return sql.toString();
      }

01-03-03. Provider class 작성 예시-Mapper 인터페이스

  • SQL 객체를 생성하고 메소드를 활용해 필요한 구문을 붙인 뒤, toString() 메소드를 사용하여 쿼리 문자열로 반환하는 구조로 작성한다.
  1. SELECT Provider

    public String searchMenuByCondition(SearchCriteria searchCriteria) {
    
        SQL sql = new SQL();
    
        sql.SELECT("A.MENU_CODE")        // select 대상 컬럼
                .SELECT("A.MENU_NAME")
                .SELECT("A.MENU_PRICE")
                .SELECT("A.CATEGORY_CODE")
                .SELECT("A.ORDERABLE_STATUS")
                .FROM("TBL_MENU A");       // select 대상 테이블
    
        if("category".equals(searchCriteria.getCondition())) {
            sql.JOIN("TBL_CATEGORY B ON (A.CATEGORY_CODE = B.CATEGORY_CODE)") // join 구문
                    .WHERE("A.ORDERABLE_STATUS = 'Y'")
                    .AND()
                    .WHERE("B.CATEGORY_NAME = #{ value }");
    
        } else if("name".equals(searchCriteria.getCondition())) {
            sql.WHERE("A.ORDERABLE_STATUS = 'Y'"
                    , "A.MENU_NAME LIKE CONCAT('%', #{ value }, '%')"); // 조건절
        }
    
        return sql.toString();
    }
    • WHERE() 내에서 가변인자를 이용하면 자동 AND로 처리하기 때문에 OR를 사용해야 하는 경우 OR()를 사용해야 한다.
  2. INSERT Provider

    public String registMenu(MenuDTO menu) {
    
        SQL sql = new SQL();
    
        sql.INSERT_INTO("TBL_MENU")         // insert 대상 테이블
                .VALUES("MENU_NAME", "#{ name }")  // (insert *대상 컬럼*, #{ *필드명* })
                .VALUES("MENU_PRICE", "#{ price }")
                .VALUES("CATEGORY_CODE", "#{ categoryCode }")
                .VALUES("ORDERABLE_STATUS", "#{ orderableStatus }");
    
        return sql.toString();
    
    }
  3. UPDATE Provider

    public String modifyMenu(MenuDTO menu) {
    
        SQL sql = new SQL();
    
        sql.UPDATE("TBL_MENU");         // update 대상 테이블
    
        if(menu.getName() != null && !"".equals(menu.getName())) {
            sql.SET("MENU_NAME = #{ name }");  // set 구문 작성
        }
    
        if(menu.getPrice() > 0) {
            sql.SET("MENU_PRICE = #{ price }");
        }
    
        if(menu.getCategoryCode() > 0) {
            sql.SET("CATEGORY_CODE = #{ categoryCode }");
        }
    
        if(menu.getOrderableStatus() != null && !"".equals(menu.getOrderableStatus())) {
            sql.SET("ORDERABLE_STATUS = #{ orderableStatus }");
        }
    
        sql.WHERE("MENU_CODE = #{ code }");   // 조건절 작성
    
        return sql.toString();
    
    }
  4. DELETE Provider

    public String deleteMenu(int code) {
    
        SQL sql = new SQL();
    
        sql.DELETE_FROM("TBL_MENU")        // delete 대상 테이블
                .WHERE("MENU_CODE = #{ code }"); // 조건절 작성
    
        return sql.toString();
    
    }

4. 핵심 요약 한 줄씩

  • if : 조건 만족할 때만 SQL 조각 추가
  • choose : 여러 조건 중 하나만 선택해서 추가
  • trim : WHERE, AND, OR, , 등을 자동 정리
  • where : 동적 WHERE 절 쉽게 작성
  • set : 동적 UPDATE SET 절 쉽게 작성
  • foreach : 컬렉션 반복해서 IN 절 등 만들기
  • bind : 복잡한 값을 미리 변수로 만들어 사용
  • Provider : Java 코드로 동적 SQL 조립 (SQL 빌더 사용)
  • @Param : Provider/SQL에서 쓸 파라미터 이름 지정
profile
백엔드

0개의 댓글