XML vs 어노테이션 — MyBatis에서 SQL을 작성하는 두 가지 방법
MyBatis에서 SQL을 XML에 쓸지, 자바 코드에 어노테이션으로 쓸지 고민되지 않나요?
둘 다 같은 SQL을 실행하지만, 관리 방식이 완전히 다릅니다. 프로젝트 규모와 쿼리 복잡도에 따라 선택이 갈립니다.
개념 정의
MyBatis는 SQL을 XML 파일 에 작성하거나, Mapper 인터페이스의 메서드에 어노테이션 으로 직접 붙이는 두 가지 방식을 제공합니다. 어떤 방식이든 최종 실행 결과는 동일하지만, 코드 구조와 유지보수 경험이 달라집니다.
XML 방식
SQL을 별도 XML 파일에 분리하는 전통적인 방식입니다.
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<select id="findByStatus" resultType="User">
SELECT id, name, email, status
FROM users
WHERE status = #{status}
ORDER BY created_at DESC
</select>
<insert id="save" parameterType="User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (name, email, status)
VALUES (#{name}, #{email}, #{status})
</insert>
</mapper>
위 XML에서 핵심은 namespace가 Mapper 인터페이스의 풀 패키지 경로와 일치해야 한다는 점입니다. id는 인터페이스의 메서드명과 매칭됩니다.
대응하는 인터페이스는 메서드 시그니처만 선언합니다.
public interface UserMapper {
List<User> findByStatus(String status);
void save(User user);
}
어노테이션 방식
SQL을 자바 코드 안에 직접 작성합니다.
public interface UserMapper {
@Select("SELECT id, name, email, status " +
"FROM users WHERE status = #{status} " +
"ORDER BY created_at DESC")
List<User> findByStatus(String status);
@Insert("INSERT INTO users (name, email, status) " +
"VALUES (#{name}, #{email}, #{status})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void save(User user);
}
XML 파일 없이 인터페이스 하나로 완결됩니다. 단순한 CRUD에서는 코드량이 줄어들고, SQL과 메서드가 같은 파일에 있어서 탐색이 빠릅니다.
비교표
| 기준 | XML | 어노테이션 |
|---|---|---|
| SQL 위치 | 별도 XML 파일 | 자바 인터페이스 내 |
| 동적 SQL | <if>, <foreach> 등 강력 | 제한적 (<script> 태그 필요) |
| 가독성 (단순 쿼리) | 파일 분리로 탐색 필요 | 메서드 바로 위에 있어서 직관적 |
| 가독성 (복잡한 쿼리) | XML 들여쓰기로 구조 파악 용이 | 문자열 연결이 지저분해짐 |
| IDE 지원 | XML 자동완성, SQL 하이라이팅 | 문자열 안이라 IDE 지원 약함 |
| 리팩토링 | 메서드명 변경 시 XML도 수정 필요 | 메서드와 SQL이 함께 이동 |
| 팀 협업 | DBA가 XML만 수정 가능 | DBA가 자바 코드를 건드려야 함 |
XML은 복잡한 쿼리와 동적 SQL 에서 강하고, 어노테이션은 단순 CRUD와 빠른 개발 에서 강합니다.
동적 SQL에서 차이가 극명해진다
어노테이션으로 동적 SQL을 작성하려면 <script> 태그를 써야 합니다.
@Select("<script>" +
"SELECT * FROM users" +
"<where>" +
" <if test='name != null'> AND name = #{name}</if>" +
" <if test='status != null'> AND status = #{status}</if>" +
"</where>" +
"</script>")
List<User> search(@Param("name") String name,
@Param("status") String status);
같은 쿼리를 XML로 작성하면 훨씬 깔끔합니다.
<select id="search" resultType="User">
SELECT * FROM users
<where>
<if test="name != null"> AND name = #{name}</if>
<if test="status != null"> AND status = #{status}</if>
</where>
</select>
문자열 안에 XML 태그를 넣는 것 자체가 가독성을 해칩니다. 조건이 3개 이상 들어가면 어노테이션 방식은 유지보수가 어려워집니다.
주의할 점
혼용 시 우선순위 충돌
같은 메서드에 어노테이션과 XML 매핑이 동시에 존재하면 XML이 우선 합니다. 이를 모르고 어노테이션만 수정하면 변경이 반영되지 않아서 "분명히 고쳤는데 왜 안 바뀌지?" 상황이 됩니다. 한 프로젝트에서 방식을 혼용할 때는 메서드별로 명확히 분리해야 합니다.
어노테이션의 컴파일 타임 한계
XML의 SQL 오류는 애플리케이션 시작 시 파싱 단계에서 잡힙니다. 반면 어노테이션의 SQL은 문자열이라 컴파일 시점에 문법 오류를 잡을 수 없습니다. 두 방식 모두 런타임 이전에 오류를 발견하려면 Mapper 빈 로딩 시점의 검증에 의존해야 합니다.
선택 기준
단순 CRUD 위주 + 소규모 프로젝트 → 어노테이션
동적 SQL 많음 + DBA 협업 → XML
기존 프로젝트에 이미 XML이 있음 → XML 유지
신규 프로젝트 + Spring Boot → 어노테이션으로 시작, 복잡해지면 XML 전환
정리
| 항목 | 설명 |
|---|---|
| XML 방식 | SQL을 별도 파일에 분리, 동적 SQL 강력, DBA 협업 용이 |
| 어노테이션 방식 | 자바 코드에 SQL 직접 작성, 단순 쿼리에 적합 |
| 동적 SQL | XML이 압도적으로 깔끔, 어노테이션은 <script> 필요 |
| 혼용 시 주의 | XML이 어노테이션보다 우선, 같은 메서드에 중복 정의 피하기 |
| 실무 추천 | 복잡한 쿼리가 예상되면 XML, 단순하면 어노테이션 |