Kim WooSup

JPA와 MyBatis를 같이 쓰기로 한 이유

2026-05-27

AI 요약

레거시 iBatis 기반 프로젝트를 Spring Boot 3로 옮기며 등록·수정·삭제처럼 도메인 규칙과 변경 의도가 코드에 드러나는 부분은 JPA로 옮기고, 대시보드·검색·통계처럼 결과 모양을 SQL이 직접 만들어내는 조회는 MyBatis로 유지하기로 했습니다. 핵심은 기술을 섞는 것이 목적이 아니라 전환 리스크와 기능 성격에 따라 바꿀 것과 유지할 것을 명확히 나누는 현실적인 결정이었습니다.

레거시 프로젝트를 Spring Boot 3 기반으로 옮기면서 데이터 접근 방식도 다시 정리해야 했다.

기존 프로젝트는 iBatis 기반이었다.

대부분의 기능은 Service에서 SQL Map을 호출하는 방식으로 동작하고 있었다.

 

마이그레이션을 하면서 이 구조를 그대로 가져가는 건 아쉬웠다.

프레임워크는 Spring Boot 3로 바꾸는데, 데이터 접근 구조는 계속 SQL 호출 중심으로 남는다면 기존의 기술부채도 같이 따라오게 된다.

 

그래서 신규 프로젝트에서는 JPA를 도입하고 싶었다.
특히 등록, 수정, 삭제처럼 데이터를 변경하는 기능은 SQL 호출보다 코드 안에서 의미가 드러나는 구조로 바꾸고 싶었다.

 

하지만 실제 SQL Map을 보다 보니 모든 쿼리를 JPA로 바꾸는 것도 좋은 선택은 아니었다.

 

대시보드, 측정 조회, 로그, 통계 화면은 SQL이 이미 화면 결과를 만드는 역할을 하고 있었기 때문에, 전부 JPA로 바꾸면 쿼리 전환보다 결과 검증 비용이 더 커질 수 있었다.

 

그래서 이번 마이그레이션에서는 JPA와 MyBatis를 같이 쓰기로 했다.

핵심은 기술을 섞어 쓰는 것이 아니라, 바꿀 부분과 유지할 부분을 나누는 것이었다.

 

 

데이터 접근 방식을 다시 봐야 했던 이유

Service

↓
↓
↓

 

이 구조에서는 기능을 이해하려면 Java 코드보다 SQL Map을 먼저 따라가야 했다.

등록, 수정, 삭제 같은 기능도 “무엇을 변경하는지”가 코드에 드러나기보다, 어떤 SQL을 호출하는지로 표현되어 있었다.

 

예를 들어 기본정보를 수정하거나, 검사 정보를 등록하거나, 기준 정보를 삭제하는 흐름은 단순 SQL 호출이 아니라 데이터를 변경하는 기능이다.

이런 부분까지 그대로 Mapper 중심으로 가져가면 신규 프로젝트에서도 기존 구조의 한계를 반복하게 된다고 생각했다.

 

하지만 반대쪽 문제도 있었다.

레거시 SQL Map에는 단순 CRUD보다 조회성 쿼리가 훨씬 많았다.

  • 검색 조건이 많은 목록 조회

  • 페이징과 건수 조회

  • 대시보드 집계

  • 통계성 조회

  • 여러 테이블 조인

  • 기존 화면에 맞춘 컬럼 계산

  • 레거시 뷰 기반 조회

 

특히 대시보드나 로그 화면은 SQL이 단순히 데이터를 가져오는 수준이 아니었다.
화면이 기대하는 결과 모양을 SQL이 직접 만들어내고 있었다.

 

이런 쿼리를 JPA로 무리하게 옮기면 마이그레이션이 아니라 쿼리 재설계에 가까워진다.
그리고 그 과정에서 기존 화면 결과가 달라질 가능성도 커진다.

 

그래서 데이터 접근 방식을 하나로 통일하기보다, 기능의 성격과 전환 리스크를 기준으로 나누기로 했다.

 

 

선택지 1. 전부 JPA로 전환하기

가장 깔끔해 보이는 선택지는 모든 데이터 접근을 JPA로 바꾸는 것이었다.

신규 프로젝트 관점에서는 기술 스택이 단순해지고, Entity와 Repository 중심으로 구조를 정리할 수 있다.

 

하지만 이번 프로젝트에는 맞지 않는다고 봤다.

레거시에는 화면 결과를 직접 만드는 SQL이 많았다.
화면을 전부 JPA로 바꾸면 기존 결과를 맞추는 검증 비용이 너무 커진다.

기술 스택은 깔끔해질 수 있지만, 마이그레이션 안정성은 오히려 떨어질 수 있었다.

 

 

선택지 2. 전부 MyBatis로 유지하기

반대로 모든 쿼리를 MyBatis로 유지하는 방법도 있었다.

이 방식은 기존 SQL을 재사용하기 쉽고, 결과 차이가 생길 가능성도 상대적으로 낮다.

 

하지만 이렇게 가면 기존의 SQL 중심 구조를 거의 그대로 가져오게 된다.

등록, 수정, 삭제까지 전부 MyBatis로 유지하면 Service는 계속 Mapper 호출 중심으로 남을 수 있다.

마이그레이션을 하면서 줄이고 싶었던 기술부채도 그대로 따라온다.

 

 

선택지 3. JPA와 MyBatis를 나눠서 사용하기

그래서 선택한 방식은 같이 쓰는 구조였다.

입력 폼 중심의 등록, 수정, 삭제는 JPA 전환 후보로 두고, 기존 SQL 결과를 유지하는 것이 중요한 조회 화면은 MyBatis를 유지하기로 했다.

 

이 방식이 기술적으로 가장 단순한 선택은 아니다.

하지만 현재 상황에서는 가장 현실적인 선택이라고 봤다.

 

새 구조로 바꿀 수 있는 부분은 바꾸고, 기존 SQL의 의미가 강한 부분은 유지한다.

중요한 것은 기술을 하나로 맞추는 것이 아니라, 전환 범위를 잘 나누는 것이었다.

 

 

JPA로 옮기기 좋은 영역

JPA는 입력 폼 중심의 기능부터 적용하기로 했다.

 

예를 들면 아래와 같은 영역이다.

  • 기본정보 등록/수정/삭제

  • 선정기준 관리

  • 검사 정보 관리

  • 부가정보 관리

  • 사용자/권한 엔티티

 

이 영역들은 비교적 테이블과 기능의 경계가 명확하다.

 

등록, 수정, 삭제 흐름도 중요하다.
단순히 데이터를 조회해서 화면에 보여주는 것보다, 사용자의 입력을 받아 업무 데이터를 변경하는 성격이 강하다.

이런 기능은 JPA로 옮기면 변경의 의미를 코드에 더 잘 드러낼 수 있다.

 

단순히 update SQL을 호출하는 대신, 아래처럼 표현할 수 있다.

Copied
01patientInfo.updateHeight(height);02patientInfo.updateWeight(weight);03patientInfo.changeStatus(status);

SQL만 보면 단순 컬럼 변경처럼 보이던 것도, 코드에서는 어떤 의미의 변경인지 드러난다.

 

 

MyBatis를 유지하기 좋은 영역

반대로 MyBatis를 유지하기로 한 영역도 많았다.

  • 대시보드

  • 측정 조회

  • 게이트웨이/인터페이스 조회

  • 복약 목록과 월간 현황

  • 수집 로그

  • 전송 로그

  • 피드백/스케줄러 통계

  • REST 수집/재전송

 

여러 테이블을 조인하고, 조건에 따라 다른 값을 계산하고, 화면에 필요한 형태로 결과를 조합해야 했다.

대시보드나 로그 화면은 특히 SQL 자체가 화면 요구사항에 가까웠다.

 

이런 쿼리들을 JPA로 옮겨도 결국 Querydsl이나 native query로 다시 작성해야 할 가능성이 높았다.

그럴 바에는 MyBatis로 버전만 올리고 결과가 기존과 같은지 검증하는 쪽이 더 안전하다고 봤다.

 

 

한 화면 안에서도 나눠서 보기

하나의 기능을 무조건 한 기술로만 처리할 필요는 없다고 봤다.

 

예를 들어 기본정보 관리 화면이 있다면 등록, 수정, 삭제는 JPA가 어울릴 수 있다.

하지만 등록 완료 목록이나 미등록 대상자 조회는 검색 조건과 조인이 들어갈 수 있다. 이런 조회는 MyBatis가 더 적합할 수 있다.

Copied
01등록 / 수정 / 삭제02→ JPA03 04목록 / 검색 / 통계성 조회05→ MyBatis

 

처음에는 한 기능 안에서 JPA와 MyBatis를 같이 쓰는 것이 어색해 보였다.

하지만 실제로는 저장과 조회가 같은 이유로 바뀌지 않는 경우가 많았다.

 

저장은 업무 규칙과 트랜잭션이 중요하고, 조회는 화면 조건과 결과 형태가 중요했다.

그래서 한 화면 안에서도 저장과 조회의 성격이 다르면 나눠서 보는 편이 더 낫다고 판단했다.

 

 

기준 없이 섞지 않기

JPA와 MyBatis를 같이 쓰기로 하면 오히려 구조가 더 지저분해질 수 있다.

 

그래서 먼저 기준을 정했다.

  • 입력 폼 중심의 등록/수정/삭제는 JPA 후보로 본다.

  • 검색 조건이 많거나 화면 전용 결과를 만드는 조회는 MyBatis를 유지한다.

  • 기존 SQL이나 뷰를 재사용해야 하면 MyBatis를 우선한다.

  • 도메인 규칙을 코드로 드러내야 하면 JPA를 우선한다.

  • 한 화면 안에서도 저장과 목록 조회의 성격이 다르면 나눠서 본다.

 

이 기준이 없으면 어떤 곳은 JPA, 어떤 곳은 MyBatis처럼 보일 수 있다.

하지만 기준이 있으면 충분히 관리할 수 있다고 생각했다.

이번 선택은 기술을 섞어 쓰기 위한 선택이 아니었다. 어디를 바꾸고, 어디를 유지할지 정하기 위한 선택이었다.

 

 

정리

이번 마이그레이션에서 JPA와 MyBatis를 같이 쓰기로 한 이유는 단순히 두 기술을 모두 사용해보고 싶어서가 아니었다.

기존 iBatis 기반 구조를 그대로 가져가면, Spring Boot 3로 옮겨도 SQL 호출 중심 구조는 크게 달라지지 않는다.

그래서 입력 폼 중심의 등록, 수정, 삭제부터는 JPA를 도입해 기술부채를 줄이고 싶었다.

 

하지만 레거시 프로젝트에는 조회 중심 SQL이 많았다. 특히 대시보드, 로그, 측정 조회, 통계 화면은 SQL 자체가 기존 화면 결과를 만드는 역할을 하고 있었다.

이런 영역까지 한 번에 JPA로 바꾸면 기존 결과를 맞추는 비용이 더 커질 수 있었다.

 

CUD처럼 데이터를 바꾸는 영역은 JPA로 옮기고, 기존 SQL 의미가 강한 조회 화면은 MyBatis로 유지하기로 했다.

이번 선택의 핵심은 기술 통일이 아니었다.

중요한 것은 새 기술로 전부 바꾸는 것이 아니라, 어디를 바꾸고 어디를 유지할지 판단하는 것이었다.

  1. 데이터 접근 방식을 다시 봐야 했던 이유
  2. 선택지 1. 전부 JPA로 전환하기
  3. 선택지 2. 전부 MyBatis로 유지하기
  4. 선택지 3. JPA와 MyBatis를 나눠서 사용하기
  5. JPA로 옮기기 좋은 영역
  6. MyBatis를 유지하기 좋은 영역
  7. 한 화면 안에서도 나눠서 보기
  8. 기준 없이 섞지 않기
  9. 정리

'마이그레이션' 카테고리의 다른 글

  • 레거시 권한 정책 이식하기→
  • 레거시 화면 이식하기→
  • 마이그레이션 - 서론→
목록으로