10.1 MyBatis 개요 및 XML 기법
JPA가 기본 CRUD와 객체 중심의 설계에 매우 강력하다면, MyBatis(마이바티스) 는 아주 복잡한 통계 쿼리를 짜거나, 동적 쿼리(조건에 따라 WHERE 절이나 ORDER BY가 바뀌는 경우), 또는 특정 벤더(PostgreSQL, Oracle 등)에 종속적인 네이티브 SQL 쿼리가 필요할 때 아주 유용하게 쓰이는 SQL 매퍼 프레임워크입니다.
실무 비즈니스 환경에서는 메인 데이터 조작을 JPA로 하고, 관리자 페이지용 복잡한 대시보드 통계 조회 쿼리 등에는 MyBatis를 혼용하여(Sub) 사용하는 전략이 대단히 많습니다.
의존성 추가와 설정
build.gradle에 MyBatis 연동 의존성을 추가합니다.
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
application.yml에 MyBatis 설정과 XML 파일 경로를 잡아줍니다.
mybatis:
mapper-locations: classpath:mapper/**/*.xml
configuration:
map-underscore-to-camel-case: true # DB의 snake_case 컬럼 결과를 자바의 camelCase 필드에 자동 매핑시켜줍니다.
Mapper 인터페이스 설계
JPA와 비슷하게 MyBatis도 인터페이스를 생성합니다. 단, @Mapper 애너테이션을 달아주어 이 인터페이스의 구현체가 XML에 작성된 SQL이라는 것을 스프링에 선언합니다.
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface UserMapper {
// 복잡한 조건(이름 일치 여부, 나이 범위 등)을 파라미터로 넘깁니다.
List<UserStatDto> searchUserStats(@Param("keyword") String keyword, @Param("minAge") Integer minAge);
}
MyBatis XML 매퍼 작성
이제 src/main/resources/mapper/UserMapper.xml 같이 XML 파일을 만들어 직접 네이티브 SQL을 꼼꼼하게 작성합니다. 이때 <if> 같은 동적 태그를 쓸 수 있다는 것이 MyBatis의 최대 장점입니다.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 위의 자바 매퍼 인터페이스 경로를 namespace에 정확하게 적어야 합니다. -->
<mapper namespace="com.myapp.repository.UserMapper">
<!-- id 속성은 인터페이스의 메서드명과 일치해야 하며, resultType로 반환할 DTO의 풀 경로를 명시합니다. -->
<select id="searchUserStats" resultType="com.myapp.dto.UserStatDto">
SELECT user_id, user_email, calculated_score AS score
FROM users
WHERE 1=1
<!-- MyBatis의 동적 쿼리: keyword 파라미터가 null이 아니면 이 조건을 덧붙임 -->
<if test="keyword != null and keyword != ''">
AND nickname LIKE CONCAT('%', #{keyword}, '%')
</if>
<if test="minAge != null">
AND age >= #{minAge}
</if>
ORDER BY created_at DESC
</select>
</mapper>
작성이 완료된 뒤 서비스 클래스에서 UserMapper를 주입받아 searchUserStats를 호출하면, 파라미터 조건에 따라 동적으로 조립된 SQL이 데이터베이스로 전송되고 결과가 DTO 리스트로 자동 변환되어 반환됩니다.