Oracle·MySQL·MSSQL 셋을 이고 사는 MyBatis, JPA+QueryDSL 로 갈아탈 것인가 — 기회비용 계산서
Oracle·MySQL·MSSQL 세 벤더를 동시에 지원하는 시스템을 MyBatis 로 운영 중이고, JPA + QueryDSL 로 옮길지 고민한다고 하자. 이 결정은 흔한 “MyBatis vs JPA” 논쟁과 결이 다르다 — 멀티 DB 라는 조건이 저울 양쪽의 추를 전부 바꿔 놓기 때문이다. 순서대로: 지금 구조의 실체 → 양쪽의 장단 → 감춰진 비용 → 기회비용 계산 프레임 → 권고.
0. 지금 구조의 실체 — MyBatis 멀티 DB 는 “SQL 을 벤더 수만큼” 이다
MyBatis 의 멀티 벤더 공식 해법은 databaseIdProvider 다 — 설정에 벤더 식별자를 등록하고, 매핑 구문마다 databaseId 로 벤더별 구문을 갈라 쓴다1. 동적 SQL(if/choose/foreach)이 이를 보조한다2.
이 구조는 정직하다. SQL 을 완전히 통제할 수 있는 대신, 벤더 차이를 흡수해 주는 층이 없으므로 차이가 나는 모든 지점에서 SQL 이 벤더 수만큼 늘어난다. 페이징(Oracle ROWNUM/FETCH FIRST vs MySQL LIMIT vs MSSQL OFFSET…FETCH), 시퀀스 vs AUTO_INCREMENT vs IDENTITY, 날짜·문자열 함수, 락 힌트 — 전부 수작업 분기다. 세 DB 를 진지하게 지원한다면 사실상 쿼리 자산의 상당 부분을 3벌로 유지·테스트하고 있는 셈이고, 이 유지비가 이 글 전체의 논의 대상이다.
1. MyBatis 에 남는 것의 장단
장점. SQL 이 곧 코드라서 실행 계획을 눈으로 확인한 그 SQL 이 그대로 나간다. 벤더 전용 기능(옵티마이저 힌트, 분석 함수, MERGE, 계층 쿼리)을 제약 없이 쓴다. DBA 와의 협업 단위가 SQL 파일이라 튜닝 왕복이 짧다. 팀이 이미 알고 있다 — 재학습 비용 0.
단점. 위에서 본 3벌 유지비가 구조적으로 계속 나간다. SQL 이 XML 문자열이라 컴파일 타임 검증이 없다 — 컬럼명 오타, 파라미터 누락은 런타임에, 그것도 해당 벤더 경로가 실행될 때에야 드러난다. 세 벤더 중 하나에서만 터지는 SQL 버그는 테스트 매트릭스도 3배로 만든다.
2. JPA + QueryDSL 로 가는 것의 장단
장점의 핵심은 정확히 당신의 고통 지점을 겨냥한다. Hibernate 는 벤더 차이를 dialect 층에서 흡수한다 — Oracle·MySQL·SQL Server 는 모두 공식 지원 dialect 다3. 페이징·식별자 생성·함수 번역이 dialect 를 거쳐 벤더별 SQL 로 렌더링되므로, JPQL/QueryDSL 로 쓴 쿼리 1벌이 세 DB 에서 돈다. Hibernate 6 부터는 dialect 가 접속된 DB 의 버전까지 감지해 적응하므로 버전별 dialect 를 고르던 시대의 관리 부담도 없다4. QueryDSL 은 그 위에 컴파일 타임 타입 안전을 얹는다 — 컬럼명 오타가 런타임 3벌 테스트가 아니라 컴파일 에러 1개로 바뀐다.
단점. ① 학습 곡선이 실재한다 — 영속성 컨텍스트·지연 로딩·N+1·플러시 타이밍은 SQL 사고방식과 다른 축의 지식이고, 익숙해지기 전까지는 “내가 쓴 적 없는 SQL” 이 나가는 프레임워크다. ② 복잡한 조회(리포트, 통계, 벤더 전용 힌트가 필요한 튜닝 쿼리)는 결국 네이티브 SQL 로 내려간다 — 그리고 네이티브로 내려간 만큼 3벌 문제가 그대로 부활한다. ③ 손으로 튜닝해 둔 기존 SQL 자산은 이관 과정에서 상당 부분 버려진다.
3. 감춰진 비용 — QueryDSL 의 거버넌스 리스크
이 결정에서 가장 자주 빠뜨리는 항목이다. 원 QueryDSL 프로젝트(com.querydsl)는 사실상 멈춰 있다. 마지막 릴리스는 5.1.0, 2024년 1월이다5. 커뮤니티는 OpenFeign 산하로 포크했고(io.github.openfeign.querydsl), 포크는 2026년 8월의 7.6 까지 정기 릴리스를 이어가고 있다6. Spring Data 공식 문서가 이 상황을 이례적으로 직접 언급한다 — “Querydsl 유지보수가 느려져 커뮤니티가 OpenFeign 산하로 포크했으며, Spring Data 는 포크를 best-effort 기준으로 지원한다”7.
보안 관점의 실례도 있다: HQL 인젝션 취약점 CVE-2024-49203 은 두 좌표계 모두에 영향이 기재돼 있고, 패치 버전은 포크 좌표(io.github.openfeign.querydsl)로 나왔다8. 즉 지금 QueryDSL 을 새로 도입한다는 것은 “원 프로젝트가 아니라 커뮤니티 포크를, Spring 의 1급 지원이 아니라 best-effort 지원 아래에서 쓴다” 는 결정을 함께 내리는 것이다. 못 쓸 물건이라는 뜻이 아니다 — 포크는 활발하다. 다만 선정 문서에 적어야 할 리스크 항목이라는 뜻이다.
참고로 대안인 jOOQ 는 이 시스템에선 무료가 아니다 — 오픈소스 에디션은 오픈소스 DB 만 지원하고, Oracle·SQL Server 는 상용 에디션 대상이다9.
4. 기회비용 계산 프레임 — 결정 변수는 하나다
지불하는 것: 매퍼 XML 재작성 공수, 튜닝 SQL 자산 폐기, 팀 재학습, 이관 기간의 이중 운영, QueryDSL 거버넌스 리스크. 얻는 것: 벤더 차이의 dialect 위임(3벌→1벌), 컴파일 타임 타입 안전, 도메인 모델 중심 설계.
그래서 결정 변수는 하나로 수렴한다. 전체 쿼리 자산 중 dialect 가 흡수할 수 있는 비율이 얼마인가. 단순 CRUD·조건 검색·페이징이 대부분이라면 멀티 DB 야말로 JPA 전환의 이득이 극대화되는 조건이다 — 단일 DB 시스템보다 절감 폭이 벤더 수만큼 곱해진다. 반대로 벤더 전용 힌트·분석 함수·프로시저 호출이 쿼리 자산의 큰 몫이라면, 그 몫은 전환 후에도 네이티브 3벌로 남으므로 지불한 비용 대비 얻는 게 얇다. 감으로 정하지 말고 매퍼 XML 을 실제로 분류해 세어 보라 — 이 한나절짜리 조사가 이 결정에서 가장 수익률 높은 작업이다.
5. 권고 — 옮기더라도 빅뱅은 아니다
MyBatis 와 JPA 는 같은 DataSource·트랜잭션 관리자 위에서 공존할 수 있다. 그러니 현실적인 경로는:
- 계측 — 매퍼 XML 전수 분류: dialect 흡수 가능 / 벤더 전용 잔존. 이 비율이 go/no-go 를 정한다.
- 파일럿 — 신규 도메인 또는 CRUD 비중이 높은 모듈 하나를 JPA+QueryDSL 로. 세 DB 모두에 대한 통합 테스트를 이때 세운다.
- 공존 운영 — 단순 조회·쓰기부터 점진 이관, 복잡 조회·튜닝 쿼리는 MyBatis 에 남긴다. “전부 옮겨야 성공” 이라는 프레임을 버리면 실패 확률이 크게 준다.
- 거버넌스 명시 — QueryDSL 좌표는 OpenFeign 포크로 시작하고, 선정 문서에 원 프로젝트 정체·best-effort 지원 사실을 기록한다.
| MyBatis 유지 | JPA+QueryDSL 전환 | |
|---|---|---|
| 벤더 차이 처리 | 수작업 3벌 (databaseId 분기) | dialect 자동 흡수, 잔존분만 네이티브 |
| 타입 안전 | 런타임 (벤더별 경로 실행 시) | 컴파일 타임 (QueryDSL) |
| 벤더 전용 기능 | 무제약 | 네이티브로 우회 (3벌 부활) |
| 재학습 비용 | 0 | 영속성 컨텍스트·N+1 등 실재 |
| 거버넌스 | MyBatis 활발 | QueryDSL 은 포크가 사실상 본류 |
한 줄 요약 — 멀티 DB 는 JPA 전환의 이득을 벤더 수만큼 증폭하는 조건이지만, 그 이득은 dialect 가 흡수 가능한 쿼리에만 발생한다. 매퍼를 세어 보고, 옮긴다면 공존으로 옮겨라.
근거의 한계
- 이관 공수(인월)·”한나절짜리 조사” 등의 규모 감각은 내 경험칙이며 정량 출처가 없다. 매퍼 수·팀 규모에 따라 크게 다르다.
- N+1·영속성 컨텍스트 학습 곡선의 크기는 정성 판단이다 — 팀의 사전 경험에 따라 0 에 가까울 수도 있다.
- MyBatis 진영과 JPA 진영의 성능 우열을 다룬 중립 헤드투헤드 벤치마크는 확인하지 못했고, 그래서 성능 주장은 이 글에 없다.
- CVE-2024-49203 의 상세 페이지(NVD)는 스크립트 렌더링이라 본문을 텍스트로 실측하지 못해, 대신 GitHub 공식 어드바이저리를 출처로 썼다.
References
-
MyBatis 공식 문서 — Configuration (
databaseIdProvider, 벤더별 구문 분기) ↩ -
MyBatis 공식 문서 — Dynamic SQL ↩
-
Hibernate ORM 공식 문서 — Supported Dialects (Oracle·MySQL·SQL Server 공식 지원 dialect) ↩
-
Hibernate ORM 6.0 공식 마이그레이션 가이드 — Migration Guide (dialect 가 DB 버전을 감지·적응, 버전별 dialect deprecated) ↩
-
GitHub — querydsl/querydsl (최종 릴리스 5.1.0, 2024-01-29) ↩
-
GitHub — OpenFeign/querydsl (커뮤니티 포크, 7.6 릴리스 2026-08-19) ↩
-
Spring Data JPA 공식 문서 — Core Extensions (Querydsl 포크 상황과 best-effort 지원 명시) ↩
-
GitHub Security Advisory — GHSA-6q3q-6v5j-h6vg (CVE-2024-49203, Querydsl HQL injection through orderBy) ↩
-
jOOQ 공식 — Download / Licensing (오픈소스 에디션의 지원 DB 목록, Oracle·SQL Server 는 상용 에디션) ↩