고객사 업무를 하다보니 MyBatis를 더 많이 접하고있다.

일반적으로 SQL 매핑 프레임워크 관련 트렌드는 JPA, Hibernate가 압도적이긴 하나 아직까지 한국에서 MyBatis 비율이 높은데에는 정부의 표준 프레임워크가 MyBatis를 채택하고 있는게 한 몫하지 않나싶다.

무튼 이러한 MyBatis 사용시 SQL이 동적으로 작성되도록 할 때 유용한 <bind>에 대해 간단히 정리해보았다.

바로 예제 코드부터. (✭ 실무에서 유용할 활용법은 예시 3번)

예시 1


<select id="getUsersByAge" resultType="User">
    <bind name="maxAge" value="age + 5" />
    SELECT * FROM users
    WHERE age BETWEEN #{age} AND #{maxAge}
</select>

예시 1

여기서 <bind> 태그는 ‘maxAge’라는 변수를 만들고 있다. 파라미터로 받은 ‘age’에 5를 더해서 5살 더 많은 사람들까지 조회하는 쿼리를 만들었다. 사실 위 예제는 괜히 코드량만 더 길게 만든 감이 있는 예제이지만, bind 태그를 잘 활용하면 다음과 같은 이점을 얻을 수 있다.

  • 코드 재사용성 ↑

  • 복잡한 로직 분리, SQL 가독성 ↑

두 개의 예시를 더 살펴보자

예시 2


<select id="searchProducts" resultType="Product">
    <bind name="searchPattern" value="'%' + keyword + '%'" />
    SELECT * FROM products
    WHERE name LIKE #{searchPattern}
       OR description LIKE #{searchPattern}
</select>

예시 2

이 예제에서는 검색 키워드를 포함하는 패턴을 만들고 있다. ‘searchPattern’이라는 변수에 앞뒤로 ‘%‘를 붙여서 부분 일치 검색이 가능하도록 만들어졌다.

이렇듯 <bind> 태그는 SQL 실행 전 java 코드로 선 처리된다는 점에서 의미가 있다. 따라서 다음에 소개할 예시와 같이 응용해볼 수 있다.

**✭ **예시 3 ✭


예시 3-1)

<sql id="autospFilterSp">
    <bind name="purchaseCdListIncludedInSp" value="@com.a.b.c.common.constant.d.EnumPurchaseType@getPurchaseCdListIncludedInSp()"/>
    AND IF(ATSP.SP_YN = 'Y', RCM.OFFR_TYP_NM NOT IN (<foreach collection="purchaseCdListIncludedInSp" item="item" separator=",">#{item}</foreach>), TRUE)
</sql>

예시 3-1

예시 3-2)

@Getter
public enum CmmtOptiSpPurchaseType {
    /** ComputeSavingsPlans */
    COMPUTE("ComputeSavingsPlans", "Compute",
            "AmazonEC2",
            "AmazonECS",
            "AmazonEKS",
            "AWSLambda"
    ),
    /** EC2InstanceSavingsPlans */
    EC2_INSTANCE("EC2InstanceSavingsPlans", "EC2 Instance",
            "AmazonEC2"
    ),
    /** SageMakerSavingsPlans */
    SAGEMAKER("SageMakerSavingsPlans", "SageMaker",
            "AmazonSageMaker"
    )

    /** 코드 */
    private final String cd;
    /** 이름 */
    private final String nm;
    /** 서비스 코드 집합 */
    private final Set<String> svcCdSet;
    
    CmmtOptiSpPurchaseType(String cd, String nm, String... svcCdArray) {
        this.cd = cd;
        this.nm = nm;
        this.svcCdSet = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(svcCdArray)));
    }
    
    ...생략...
    
    public static List<String> getPurchaseCdListIncludedInSp() {
        return Arrays.asList(EnumPurchaseType.COMPUTE.getCd(), EnumPurchaseType.EC2_INSTANCE.getCd());
    }
}

예시 3-2

설명:

  1. com.a.b.c.common.constant.d 패키지 경로의 EnumPurchaseType라는 이름의 enum에서 getPurchaseCdListIncludedInSp 메서드를 실행 후 그 return값을 purchaseCdListIncludedInSp 변수로 설정 ※ return값: [“ComputeSavingsPlans”, “EC2InstanceSavingsPlans”]

  2. MyBatis <foreach>를 통해 동적 SQL문 구현

💡
작성될 SQL AND IF(ATSP.AUTOSP_YN = 'Y', RCM.OFFR_TYP_NM NOT IN ('``ComputeSavingsPlans’, ‘EC2InstanceSavingsPlans’)

앞선 두 예시는 뭔가 쿼리로도 해결할 수 있는 내용을 더 번잡하게 만든 것 같은 느낌이 든다.

하지만 세 번째 예시 처럼, <bind> 태그는 이미 구현되어있는 Enum 코드를 재사용하거나 method return 값을 통해 SQL을 동적으로 생성할 수 있고, 하드코딩과 중복코드를 피할 수 있다는 점에서 가치가 있다.