마이바티스 매퍼 방식은 어떤것을 사용해야 할까?

반응형

마이바티스에서는 SQL을 매핑하는 방식으로 크게 어노테이션 방식과 XML 방식을 제공하고 있습니다. 저는 이 중 어노테이션 방식을 선택하게 되었습니다. 가장 큰 이유는 멀티 모듈 구조에서 조금 더 관리하기 좋다고 느꼈기 때문입니다. 특히 어떤 기능이 어디에 존재하는지 추적하는 과정에서 어노테이션 방식이 더 직관적이라고 생각했습니다. 물론, 단점도 존재합니다. 복잡한 동적 쿼리를 작성하게 되면 XML 방식에 비해 불편함이 생길 수 있습니다. 그럼에도 불구하고 어노테이션 방식을 선택한 이유와, 왜 멀티 모듈 환경에서 잘 어울린다고 생각했는지 이번 글에서 정리해보려고 합니다.

어노테이션 방식은 어떻게 사용할까?

어노테이션 방식은 Mapper 인터페이스 위에 SQL을 직접 작성하여 사용하는 방식입니다.
기존 XML처럼 별도의 mapper.xml 파일을 생성하는 것이 아니라, 자바 코드 내부에서 SQL을 함께 관리하게 됩니다.

예를 들어 간단한 조회는 다음과 같이 작성할 수 있습니다. 

빨간건 스키마가 없기때문이다. 예제라 스키마는 추가하지 않았다. 실제로 동작하는건 중요하지 않는다.

이렇게 작성하게 되면 Mapper 인터페이스만 확인해도 어떤 SQL이 실행되는지 바로 확인할 수 있습니다.
즉, XML 파일로 이동하지 않아도 되기 때문에 코드 흐름을 따라가기 조금 더 수월하다고 느껴졌습니다.

또한 멀티 모듈 환경에서는 mapper.xml 파일이 resources 하위로 분리되면서 위치를 추적해야 하는 경우가 생길 수 있습니다. 반면 어노테이션 방식은 인터페이스와 SQL이 함께 존재하기 때문에, 도메인이 증가하더라도 상대적으로 구조를 파악하기 쉽다고 느껴졌습니다.

다만 SQL이 길어질수록 가독성이 급격하게 떨어질 수 있다는 단점 역시 존재합니다.
특히 동적 쿼리가 많아질수록 XML 방식이 더 적합할 수 있기 때문에, 프로젝트 상황에 따라 혼합하여 사용하는 것도 고려할 수 있다고 생각합니다.

그래서 Annotation 방식을 선택한 이유가 무엇일까?

제가 생각하는 Annotation 방식의 가장 큰 장점은 바로 "추적"입니다. 만약, XML 방식으로 작성하게 된다면 아래와 같이 resources 하위에 mapper xml 파일이 생성됩니다.

그렇다면 user 도메인에 대한 매퍼를 찾는다고 가정해보겠습니다. 이 경우 app 영역에서는 자바 코드를 따라가다가, 실제 SQL을 확인하려면 다시 infrastructure 모듈의 resources 영역으로 이동해야 합니다. 특히 멀티 모듈 구조에서는 infrastructure 모듈에 여러 설정 파일과 라이브러리 관련 리소스가 함께 존재하는 경우가 많습니다. 즉, 개발자는 자바 코드와 resources 영역을 계속 오가며 흐름을 추적해야 합니다.

게다가 resources 영역에는 mapper xml 뿐만 아니라 application.yml 같은 설정 파일도 함께 존재합니다. 결국 도메인이 증가할수록 "어떤 기능이 어디에 존재하는지" 파악하는 비용 역시 함께 증가한다고 느꼈습니다. 반면 Annotation 방식은 인터페이스와 SQL이 함께 존재하기 때문에, 코드 흐름을 따라가기가 조금 더 직관적이라고 생각했습니다.

이러한 이유로 멀티 모듈 환경에서 마이바티스를 적용할 때 Annotation 방식을 선호하게 되었습니다.

하지만 여기서 한 가지 의문이 생깁니다.
최근에는 AI를 활용해 코드 흐름을 추적하는 경우가 많아졌습니다.
그렇다면 "추적이 쉽다"는 Annotation 방식의 장점은 이전보다 약해진 것이 아닐까요?

AI 입장에서는 SQL이 Annotation에 있든 XML에 있든, 관련 파일을 함께 읽어낼 수 있다면 큰 차이가 없을 수 있습니다.
그럼에도 불구하고 멀티 모듈 환경에서 Annotation 방식을 선택하는 것이 여전히 의미가 있을까요?

AI 시대에 이 선택이 여전히 의미가 있는가?

그럼에도 불구하고 의미는 있다고 생각합니다. 이유는 AI가 코드를 작성하고 리뷰를 도와주더라도, 최종적으로 그 코드를 판단하는 주체는 사람이기 때문입니다. AI는 XML과 Annotation을 모두 읽을 수 있지만, 사람은 최종 리뷰어로서 변경된 코드가 적절한지 확인해야 합니다.

이때 Annotation 방식은 Mapper 인터페이스와 SQL이 함께 존재하기 때문에, 리뷰하는 입장에서 변경 범위를 더 빠르게 파악할 수 있습니다. 즉, 단순히 "AI가 찾을 수 있느냐"의 문제가 아니라, 사람이 최종적으로 검토할 때 얼마나 적은 맥락 이동으로 판단할 수 있느냐의 문제라고 생각했습니다. 그래서 AI가 개발을 도와주는 환경에서도 Annotation 방식은 여전히 의미가 있다고 생각합니다.

특히, 멀티 모듈처럼 구조가 나뉘어 있는 환경에서는 코드와 SQL이 가까이 있는 것만으로도 최종 리뷰 비용을 줄일 수 있다고 느꼈습니다.

지금까지 멀티 모듈 환경에서 Annotation 방식을 선택한 이유에 대해 알아봤습니다.
그렇다면 이 선택을 하면서 어떤 것을 포기해야 할까요? Annotation 방식은 코드와 SQL을 가까이 둘 수 있다는 장점이 있지만, 그만큼 XML 방식이 가지고 있던 장점 일부를 내려놓아야 합니다. 특히 복잡한 동적 쿼리를 작성하거나 SQL을 별도로 관리해야 하는 상황에서는 Annotation 방식이 오히려 불편하게 느껴질 수 있습니다.

Annotation 방식을 선택하면 포기해야 하는 것들

그렇다면 XML 방식이 가장 강점을 가지는 부분은 무엇일까요?
바로 복잡한 SQL과 동적 쿼리를 관리하는 부분입니다.
특히 문자열 내부에 조건문이나 반복문이 계속 추가되는 구조에서는 Annotation 방식보다 XML 방식이 훨씬 깔끔하게 느껴질 수 있습니다. Annotation 방식 역시 동적 쿼리를 작성할 수는 있습니다. 하지만 문자열 내부에 다시 문자열을 작성해야 하는 구조가 반복되다 보니, 쿼리가 복잡해질수록 가독성이 빠르게 떨어질 수 있습니다.

반면 XML 방식은 SQL 자체를 분리해서 관리하기 때문에, 복잡한 쿼리일수록 오히려 구조를 파악하기 쉬워진다고 느꼈습니다.

여기서 선택지는 크게 두 가지가 있습니다.

첫 번째는 XML과 Annotation을 함께 사용하는 하이브리드 방식입니다.
단순한 쿼리는 Annotation으로 작성하고, 복잡한 동적 쿼리는 XML로 분리하는 방식입니다.

두 번째는 Annotation 방식을 유지하되, SQL Provider와 같은 별도의 클래스를 사용하는 방식입니다.
이 경우 기존 Mapper 인터페이스 안에서 모든 SQL을 처리하는 것이 아니라, SQL을 생성하는 책임을 다른 클래스로 분리하게 됩니다.

결국 Annotation 방식을 선택한다는 것은 단순히 XML을 사용하지 않는다는 의미가 아닙니다.
복잡한 쿼리가 등장했을 때, 그 복잡도를 어디로 옮길 것인지 함께 고민해야 한다는 의미에 가깝습니다.

이 글에서는 후자의 방법을 중심으로 고민해보려고 합니다.

Annotation 방식을 유지하면서도 복잡한 쿼리를 어떻게 다룰 수 있을지 살펴보고, 어느 정도 복잡도가 높아졌을 때 XML 방식을 고려해야 하는지도 함께 정리해보겠습니다.

Annotation 방식을 유지하면서 복잡한 쿼리를 다루는 방법

Annotation 방식을 살펴보기 이전에  XML은 어떻게 복잡한 쿼리를 다루는지 간단하게 알아봅시다. 

이 코드는 CTE(WITH 절)와 서브쿼리를 이용해 사용자별 주문 합계를 구한 뒤, 주문 금액을 기준으로 오름차순 정렬하는 쿼리입니다.
그렇다면 이 쿼리를 Annotation 방식으로 작성하면 어떻게 될까요?

작성된 이 쿼리를 그대로 옮겨 보겠습니다.

이처럼 순수 SQL만 존재하는 경우에는 Annotation 방식에서도 거의 그대로 옮겨 사용할 수 있습니다.
다만 XML에서 사용하던 SQL을 그대로 복사하는 경우에는 XML 엔티티 escape를 확인해야 합니다. 예를 들어, &gt;는 >로, &lt;는 <로 변경해줘야 합니다. 즉, XML 내부에서 사용하던 쿼리를 Annotation 방식으로 옮길 때는 단순히 복사해서 붙여넣는 것이 아니라, XML 엔티티 escape가 남아 있지는 않은지 확인하는 과정이 필요합니다.

하지만, <if> <foreach> <where>을 사용하는 경우에는 어떨까요?

이처럼 조건에 따라 SQL이 동적으로 변경되는 쿼리를 동적 쿼리라고 합니다.
마이바티스 XML 방식에서는 if, choose, foreach 같은 태그를 이용해 이러한 동적 쿼리를 처리할 수 있습니다. 
그렇다면, Annotation 방식에도 똑같이 적용해보겠습니다.

하지만 XML에서 사용하던 태그를 Annotation 방식에 그대로 옮기자 컴파일 예외가 발생했습니다. 그렇다면 왜 Annotation 방식에서는 XML 태그를 그대로 사용할 수 없을까요? 근본적인 이유는 Annotation 방식은 문자열이고, XML 방식은 문서 구조이기 때문입니다.

XML 방식에서는 <if>, <foreach> 같은 태그 자체가 XML 구조의 일부로 인식됩니다. 그래서 마이바티스가 해당 태그를 읽고 동적 SQL로 변환할 수 있습니다. 반면, Annotation 방식에서는 SQL이 단순 문자열로 전달됩니다. 즉, <if> 같은 내용이 들어가더라도 구조가 아니라문자열로 취급되기 때문에 그대로 해석할 수 없습니다.

문자열은 취급할  수 없는 이유

그렇다면, 문자열로 취급을 하면 어째서 해석하지 못하는 걸까요? 문자열은 단순 데이터이기 때문입니다.
예를 들어 Annotation 방식에서는 SQL이 아래처럼 자바 문자열로 전달됩니다.

java
@Select("select * from user")

즉, 자바 입장에서는 이것을 단순한 문자 집합으로만 인식합니다. <if> 역시 마찬가지입니다.

@Select("<if test='id != null'> ... </if>")

이렇게 작성하더라도 자바는 이것이 조건문 구조인지 알지 못합니다. 그냥 <, i, f 로 이루어진 문자열일 뿐입니다.반면, XML 방식에서는 <if>가 문자열이 아니라 XML 문서의 태그 구조로 존재합니다. 그래서 마이바티스가 XML을 파싱하면서 이건 조건문이구나라고 해석할 수 있습니다. 즉, 핵심은 단순합니다. 문자열은 의미를 가지지 않지만, XML 태그는 구조를 가지기 때문에 마이바티스가 동적 SQL로 해석할 수 있는 것입니다.

그렇다면, Annotation방법을 사용할때는 동적 쿼리를 사용하지 못할까요?

다행스럽게도 Annotation 방식에서도 동적 쿼리를 사용할 수 있습니다.

다만 XML 방식처럼 태그를 바로 사용하는 것이 아니라, 마이바티스가 동적 SQL로 해석할 수 있는 형태로 만들어줘야 합니다.

그 과정에 대해 조금 더 자세히 확인해보겠습니다.

마이바티스에서는 이러한 문제를 Provider를 통해 해결할 수 있습니다.
기존 Mapper 인터페이스 내부에 모든 SQL을 작성하는 것이 아니라, 별도의 클래스에서 동적 SQL을 생성하도록 분리하는 방식입니다.

즉, Annotation 방식은 유지하면서도 복잡한 동적 쿼리를 처리할 수 있도록 만들어주는 구조라고 볼 수 있습니다.

즉, Annotation 방식을 유지하면서도 복잡한 동적 쿼리를 처리할 수 있도록 만들어주는 구조라고 볼 수 있습니다. 코드를 확인해보면 SQL 객체를 생성한 뒤, SELECT, FROM, WHERE 절에 해당하는 메소드를 사용하는 것을 볼 수 있습니다.  또한, 자바 코드 기반으로 동작하기 때문에, 전달받은 파라미터 값을 기준으로 조건을 분기하여 동적 쿼리를 구성할 수 있습니다.

하지만 이것은 실제 SQL 자체가 아닙니다. 정확히는 SQL을 생성하기 위한 Provider 클래스에 가깝습니다.
그렇다면 이렇게 생성한 SQL은 어디에서 사용되는 것일까요? 마이바티스는 Mapper 인터페이스에서 Provider 클래스를 참조하여, 실행 시점에 동적으로 SQL을 생성한 뒤 실제 쿼리로 변환하여 사용하게 됩니다.

이를 해석해보면 @SelectProvider를 통해 Provider 클래스가 생성한 SQL을 실행하라는 의미입니다. 즉, Mapper 인터페이스 내부에서 직접 SQL을 작성하는 것이 아니라, 별도의 Provider 클래스에서 생성한 SQL을 가져와 실행하는 구조라고 볼 수 있습니다.

다시 코드를 자세히 확인해보면 문장 끝마다 `;`를 붙이고 있는 것을 볼 수 있습니다. 즉, 결국 SQL을 문자열이 아닌 자바 코드 형태로 조합하고 있다는 뜻입니다. 이 때문에 조건이 많아지고 쿼리 복잡도가 올라갈수록 가독성이 빠르게 떨어질 수 있습니다. 특히 단순 SQL이 아니라 여러 조건과 분기가 추가되기 시작하면, 실제 SQL을 작성하는 것보다 자바 문법을 맞추는 데 더 집중하게 되는 경우도 발생할 수 있습니다. 

현재 수준의 복잡도에서는 충분히 사용할 수 있는 방식이라고 느껴졌습니다. 하지만 쿼리 복잡도가 계속 증가하는 경우에는 Annotation 방식만 고집하기보다는, XML 방식을 함께 사용하는 하이브리드 구조 역시 고려할 필요가 있다고 생각합니다. 특히 동적 조건이 많아지고 SQL 길이가 길어질수록 XML 방식이 오히려 더 읽기 쉽고 유지보수에 유리할 수 있습니다.

결론


특정 구조나 기술을 선택할 때는 단순히 유명한 기술인가를 보는 것이 아니라, 왜 이 구조를 선택했는지와 어떤 트레이드오프가 존재하는지를 함께 고민해야 한다고 생각합니다. 저 역시 SQL 매퍼 기술을 선택하는 과정에서 ORM인 JPA 대신 마이바티스를 선택하였습니다. 그 이유는 현재 프로젝트에서는 마이바티스가 JPA보다 SQL 흐름을 직접 제어하고 접근하기 더 쉽다고 느꼈기 때문입니다. 또한 프로젝트 구조를 멀티 모듈로 구성하였기 때문에, Annotation 방식이 XML 방식보다 더 잘 맞는다고 생각하였습니다. 같은 Java 코드 흐름 안에서 SQL을 함께 관리할 수 있기 때문에, 구조를 추적하고 흐름을 이해하기 조금 더 직관적이라고 느꼈습니다. 하지만 동적 쿼리를 작성하기 시작하면서 문제 역시 존재하였습니다. 복잡한 조건이 증가할수록 XML 방식보다 Annotation 방식의 복잡도가 더 빠르게 증가하였기 때문입니다.
그럼에도 불구하고 Annotation 방식을 선택한 이유는, 동적 쿼리를 작성하는 복잡성보다 프로젝트 전체 흐름을 추적하고 제어하는 것이 더 중요하다고 판단하였기 때문입니다. 물론, 프로젝트 규모와 복잡도에 따라 XML 방식이 더 적절한 상황 역시 충분히 존재할 수 있습니다. 결국 중요한 것은 어떤 방식이 무조건 더 좋다가 아니라, 현재 프로젝트 구조에서 어떤 방식이 더 적절한지를 계속 고민하는 것이라고 생각합니다.

반응형

댓글

Designed by JB FACTORY