MyBatis XML 매핑 태그 백서: SQL 정의부터 동적 쿼리 구조화까지

1. SQL 명령어 정의 태그

기본적인 데이터베이스 조작 문을 XML 파일 내에 선언할 때 사용하는 태그입니다. 각 쿼리 종류별로 고유한 속성을 가지며, 파라미터와 반환값의 타입을 명확히 정의해야 합니다.

  • <select>: 데이터를 조회하는 문장. id는 네임스페이스 내 고유 식별자, parameterType은 입력값 유형, resultType 또는 resultMap은 조회 결과를 매핑할 대상 클래스를 지정합니다. 컬렉션 반환 시에는泛型(제네릭) 타입의 기본형을 입력해야 합니다.
  • <insert>, <delete>, <update>: 생성, 삭제, 수정 작업을 수행합니다. 주로 parameterType만으로 파라미터 타입을 정의하며, useGeneratedKeys나 keyProperty를 조합하면 자동 발급된 PK를 자바 객체에 주입할 수 있습니다.
<select id="findUserByPk" parameterType="Long" resultType="com.app.dto.UserDTO">
    SELECT user_seq, login_id, email, join_date FROM users WHERE user_seq = #{pk}
</select>

<insert id="insertUser" parameterType="com.app.vo.UserVO" useGeneratedKeys="true" keyProperty="userSeq">
    INSERT INTO users (login_id, email, role)
    VALUES (#{loginId}, #{email}, #{role})
</insert>

<delete id="removeUser" parameterType="Long">
    DELETE FROM users WHERE user_seq = #{pk}
</delete>

<update id="updateUserRole" parameterType="com.app.vo.UserVO">
    UPDATE users SET role = #{newRole}, updated_at = CURRENT_TIMESTAMP WHERE user_seq = #{userSeq}
</update>

2. 결과 집합과 자바 객체 매핑 (ResultMap)

테이블 컬럼명과 자바 클래스 필드명이 일치하지 않거나, 대소문자 차이, 특수 문자가 포함된 경우 명시적으로 필드를 연결해야 합니다. <resultMap>는 이러한 불일치를 해소하고 복잡한 중첩 구조나 조인 결과를 정교하게 바인딩하는 역할을 합니다.

  • id 속성: 기본키에 해당하는 필드 매핑. 성능 최적화를 위해 반드시 지정해야 합니다.
  • result 속성: 일반 컬럼과 DTO/Entity 필드 연결. 필요 시 jdbcType을 명시하여 NULL 처리 오류를 방지합니다.
<resultMap id="UserDetailMap" type="com.app.dto.UserDTO">
    <id property="seq" column="USER_SEQ" jdbcType="BIGINT"/>
    <result property="username" column="LOGIN_ID"/>
    <result property="contactEmail" column="EMAIL_ADDR"/>
    <result property="registrationDt" column="JOIN_DATE" jdbcType="TIMESTAMP"/>
    <result property="accountStatus" column="STATUS_FLAG" jdbcType="INTEGER"/>
</resultMap>

<select id="getUserDetail" resultMap="UserDetailMap" parameterType="Long">
    SELECT USER_SEQ, LOGIN_ID, EMAIL_ADDR, JOIN_DATE, STATUS_FLAG 
    FROM users WHERE user_seq = #{pk}
</select>

3. 동적 SQL 제어문

실행 시점의 파라미터 값에 따라 쿼리 구조를 유연하게 변경해야 할 때 사용합니다. MyBatis의 핵심 기능으로, OGNL(expression) 기반 테스트 구문을 제공합니다.

  • <if>: 조건이 참일 때만 해당 SQL절을 삽입합니다. 주로 필터 조건이나 업데이트 대상 필드 선택에 활용됩니다.
  • <foreach>: List, Array, Map 등의 컬렉션을 순회하여 SQL 조각을 반복 생성합니다. BATCH_insert나 IN 조건 구축에 필수적입니다.
  • <choose>, <when>, <otherwise>: Java의 switch-case 구조와 동일합니다. 여러 조건 중 첫 번째로 만족하는 when 절만 실행되며, 모두 불일경우 otherwise가 fallback 됩니다.
<if test="keyword != null and keyword != ''">
    AND login_id LIKE CONCAT('%', #{keyword}, '%')
</if>

<select id="findUsersByIds" resultMap="UserDetailMap">
    SELECT user_seq, login_id FROM users WHERE user_seq IN
    <foreach item="targetSeq" collection="idsList" open="(" separator="," close=")">
        #{targetSeq}
    </foreach>
</select>

<select id="applyFlexibleFilter" resultMap="UserDetailMap">
    SELECT * FROM users
    <where>
        <choose>
            <when test="activeOnly">
                AND account_status = 'ENABLED'
            </when>
            <when test="registeredAfter != null">
                AND join_date >= #{registeredAfter}
            </when>
            <otherwise>
                AND deleted_at IS NULL
            </otherwise>
        </choose>
    </where>
</select>

4. SQL 절 서식 자동 조정 (Clause Formatting)

동적 조건이混杂될 때 발생하는 문법 오류(WHERE AND 또는 끝없는 ,)를 자동으로 치유하거나 감싸주는 헬퍼 태그들입니다.

  • <where>: 내부에 유효한 SQL절이 하나 이상 포함되면 WHERE 키워드를 자동으로 붙이고, 선두의 AND/OR 연산자를 분리해 줍니다.
  • <set>: UPDATE 문에서 SET关键字를 관리합니다. trailing comma(최종 쉼표)를 자동으로 제거하며, 조건이 하나도 충족되지 않으면 SET 절 자체를 생략합니다.
  • <trim>: <where>와 <set>를 직접 구현하는 범용 도구입니다. prefix, suffix, prefixOverrides, suffixOverrides 속성으로 전후 공백/연산자를 자유롭게 제어할 수 있습니다.
<select id="dynamicSearch" resultMap="UserDetailMap">
    SELECT user_seq, login_id FROM users
    <where>
        <if test="departmentId != null">
            AND dept_code = #{departmentId}
        </if>
        <if test="keyword != null and keyword != ''">
            AND email LIKE '%' || #{keyword} || '%'
        </if>
    </where>
</select>

<update id="partialUpdateUser">
    UPDATE users
    <set>
        <if test="displayName != null">display_name = #{displayName},</if>
        <if test="contactPhone != null">phone_number = #{contactPhone},</if>
        <if test="isPublic != null">profile_visibility = #{isPublic}</if>
    </set>
    WHERE user_seq = #{targetSeq}
</update>

<update id="customTrimUpdate">
    UPDATE users
    <trim prefix="SET " suffixOverrides=", ">
        <if test="role != null">role = #{role},</if>
        <if test="score != null">experience_score = #{score},</if>
    </trim>
    WHERE user_seq = #{targetSeq}
</update>

5. SQL 코드 재사용 패턴

복수 개의 쿼리에서 공통으로 사용되는 컬럼 리스트나 조건절 블록을 추출하여 중복을 제거하고 유지보수를 용이하게 하는 패턴입니다.

  • <sql>: 재사용 가능한 SQL 단위를 id로 정의합니다. 동적 지시어를 포함해도 무방합니다.
  • <include>: 정의된 <sql> 블록을 refid로 인라인 삽입합니다. <with-param>를 통해 값을 전달할 수도 있습니다.
<!-- 공통 검색 필드 정의 -->
<sql id="CommonSelectColumns">
    user_seq, login_id, display_name, phone_number, is_active
</sql>

<!-- 공통 필터 조건 정의 -->
<sql id="BaseFilterConditions">
    WHERE 1=1
    <trim suffixOverrides="AND OR">
        <if test="minAge != null">AND age >= #{minAge}</if>
        <if test="maxAge != null">AND age <= #{maxAge}</if>
        <if test="regionCd != null">AND region_cd = #{regionCd}</if>
    </trim>
</sql>

<!-- 재사용 예시: 전체 조회 -->
<select id="listAllUsers" resultMap="UserDetailMap">
    SELECT
        <include refid="CommonSelectColumns"/>
    FROM users
    <include refid="BaseFilterConditions"/>
    ORDER BY user_seq DESC
</select>

<!-- 재사용 예시: 페이징 쿼리 -->
<select id="getPagedUsers" resultMap="UserDetailMap">
    SELECT u.* FROM (
        SELECT rownum as rn, base.* FROM (
            SELECT
                <include refid="CommonSelectColumns"/>
            FROM users
            <include refid="BaseFilterConditions"/>
        ) base
        WHERE rownum <= #{endRow}
    ) u WHERE rn > #{startRow}
</select>

태그: MyBatis XML Mapper 동적 SQL ResultMap OGNL 표현식

10월 2일 00:57에 게시됨