“쿼리가 느린 게 아니라 모델이 거짓말을 하고 있다.”


1. 느린 쿼리는 결과, 원인은 모델 불일치

쿼리가 느릴 때 현장에서 제일 먼저 나오는 대응은 이겁니다.

  • 인덱스 추가
  • 힌트
  • 서브쿼리 제거
  • 조인 줄이기

근데 이걸로 안 끝나는 케이스는 거의 다 이겁니다.

모델이 현실을 제대로 표현하지 못하고 있다

그래서 DB가 “말을 돌려서” 답을 내느라 느려지는 겁니다.


2. 모델이 거짓말할 때 나타나는 전형적인 징후

아래 중 두 개 이상이면 99% 모델 문제입니다.

1️⃣ 의미가 모호한 컬럼

  • status, type, flag, etc1
  • 값이 1,2,3인데 의미는 문서에만 있음

→ 쿼리가 조건 분기 지옥으로 감


2️⃣ 시간 개념이 섞여 있음

  • 생성일 / 수정일 / 적용일 / 종료일이 한 테이블에 뒤섞임
  • “현재 유효한 것”을 구하기 위해 조건이 5개 이상

→ DB는 현재가 뭔지 계산해야 하는 상태


3️⃣ N:M을 숨긴 1:N

  • 사실 이력 테이블인데 최신 row만 쓰는 구조
  • group by + max + join

→ “최신”이라는 개념을 쿼리로 발명 중


4️⃣ 집계 책임이 DB에 떠넘겨짐

  • 실시간 통계
  • 조건별 합계
  • 상태별 카운트

→ DB가 비즈니스 판단까지 대신함


3. 그래서 쿼리가 ‘느려 보이는’ 이유

DB는 거짓말을 그대로 믿고 실행합니다.

  • “이 row는 하나야” → 실제로는 수백 개
  • “상태 하나면 끝나” → 사실상 상태 머신
  • “이 값은 불변” → 이력 덕지덕지

결과적으로 옵티마이저는

  • 잘못된 카디널리티
  • 틀린 실행 계획
  • 불필요한 풀스캔

을 선택하게 됩니다.

쿼리가 느린 게 아니라, DB가 혼란스러운 상태인 거죠.


4. 해결은 튜닝이 아니라 “진실한 모델링”

이럴 때 정공법은 명확합니다.

✔ 상태를 쪼갠다

  • 현재 상태 / 이력 상태 분리
  • 의미 단위로 컬럼 분해

✔ 시간을 명시한다

  • effective_from, effective_to
  • “현재”를 컬럼으로 만들지 말고 테이블로 만든다

✔ 책임을 옮긴다

  • 집계는 배치/캐시/전용 테이블
  • 조회용 모델 따로 둠 (CQRS 개념)

✔ 쿼리가 아니라 질문을 바꾼다

  • “이걸 한 번에 구할 수 있나?”
  • “이걸 실시간일 필요가 있나?”

5. 한 줄 요약 (리뷰용)

DB가 느리게 답하는 건,
모델이 현실을 정직하게 설명하지 못하고 있기 때문이다.

LIST

1. 쓰기 비용이 바로 늘어납니다

  • INSERT / UPDATE / DELETE 시 테이블 + 모든 인덱스를 같이 수정
  • 인덱스 많을수록
    • 트랜잭션 시간 증가
    • 락 유지 시간 증가
    • TPS 하락

👉 쓰기 많은 테이블에 인덱스 남발 = 성능 하락 보증수표


2. 디스크 & 메모리 비용

  • 인덱스는 보조 구조물
  • 실제로는
    • 데이터보다 인덱스가 더 큰 경우도 흔함
  • 버퍼 캐시 점유
    • 인덱스가 캐시 먹으면
    • 정작 필요한 데이터 페이지가 밀려남

👉 “조회 빨라짐” 대신 메모리 경쟁 발생


3. 옵티마이저 판단 비용

  • 인덱스가 많을수록
    • 실행계획 탐색 비용 증가
    • 잘못된 인덱스 선택 확률 증가
  • 특히
    • 컬럼 카디널리티 낮은 인덱스
    • 조건 순서 어정쩡한 복합 인덱스

👉 있다고 쓰는 게 아니라, 맞아야 씁니다


4. 인덱스는 ‘쿼리 계약’이다

인덱스 하나 추가 =

“이 쿼리는 앞으로도 중요하다”라는 운영 계약

  • 쿼리 없어졌는데 인덱스는 남아 있음
  • 요구사항 바뀌었는데 인덱스 구조는 과거 기준

👉 기술 부채 중에서도 가장 조용히 쌓이는 부채


5. 그래서 실무 원칙은 이겁니다

❌ 이렇게 하면 안 됨

  • “혹시 모르니까”
  • “느릴 것 같아서”
  • “PK 외엔 다 인덱스”

✅ 이렇게 합니다

  • 실제 슬로우 쿼리 기준
  • WHERE + ORDER BY + JOIN 패턴 기준
  • 카디널리티 높은 컬럼 우선
  • 복합 인덱스는 순서가 핵심
  • 쓰기 많은 테이블은 최소 인덱스

한 줄 요약 (현장 멘트용)

인덱스는 성능 옵션이 아니라, 쓰기·메모리·운영 비용을 지불하고 사는 구조다.

LIST

과거에 주로 사용되던 Commons DBCPC3P0 같은 "옛날 스타일"의 커넥션 풀과 현재 표준인 HikariCP는 설계 철학부터 성능 최적화 방식까지 큰 차이가 있습니다.

단순히 "더 빠르다"를 넘어, 어떤 기술적 포인트가 다른지 비교해 정리해 드립니다.


1. 한눈에 보는 비교 (Old vs. Modern)

구분 옛날 스타일 (DBCP, C3P0) 요즘 스타일 (HikariCP)
철학 풍부한 기능과 세밀한 옵션 제공 극단적인 단순화와 성능 최적화
라이브러리 크기 상대적으로 무거움 (수 MB) 매우 가벼움 (약 130KB)
동기화 전략 synchronized 등 무거운 락 위주 Lock-free 설계 (ConcurrentBag)
연결 검증 testOnBorrow, validationQuery 위주 maxLifetime 기반의 선제적 교체
바이트코드 표준 JDK Proxy 사용 Javassist를 이용한 바이트코드 최적화

2. 결정적인 기술적 차이점

① 연결 관리의 패러다임: "검사" vs "교체"

  • 옛날 방식: "이 커넥션 아직 살아있나?"라고 끊임없이 DB에 물어봅니다 (test-while-idle, testOnBorrow). 이 과정 자체가 DB 부하가 되고 응답 속도를 늦춥니다.
  • HikariCP 방식: 커넥션의 **유효 수명(maxLifetime)**을 정해두고, 시간이 다 되면 조용히 폐기하고 새것으로 바꿉니다. 굳이 살아있는지 찔러보지 않아도 신선한 상태를 유지하는 전략입니다.

② 동기화 구조: ConcurrentBag

옛날 풀들은 여러 스레드가 동시에 커넥션을 요청하면 줄을 세우기 위해 무거운 락(Lock)을 걸었습니다. 반면, HikariCP는 ConcurrentBag이라는 특수한 구조를 사용합니다.

  • ThreadLocal 캐싱: 내가 썼던 커넥션을 우선적으로 다시 찾게 해줍니다.
  • Queue-stealing: 내 주머니에 없으면 다른 스레드의 주머니에서 남는 걸 가로채 옵니다. 이 과정에서 락 경합을 최소화하여 성능을 폭발적으로 끌어올렸습니다.

③ 바이트코드 수준의 다이어트

HikariCP 개발자들은 CPU 캐시 적중률까지 고려했습니다. 코드를 극단적으로 줄여서 컴파일된 바이트코드가 CPU 캐시에 더 잘 올라가도록 설계했습니다. 심지어 Java의 표준 Proxy 객체가 무겁다고 판단해, Javassist라는 도구로 직접 최적화된 프록시를 생성해서 씁니다.


3. 설정 시 주의사항 (마이그레이션 팁)

과거 설정 방식에 익숙하다면 HikariCP를 쓸 때 당황할 수 있는 부분들입니다.

  • minimumIdle 설정 지양: 과거에는 커넥션을 미리 많이 만들어두는 게 미덕이었으나, HikariCP는 maximumPoolSize와 동일하게 맞추는 Fixed Pool 방식을 강력히 권장합니다. (풀 크기가 변하면 그 자체가 오버헤드이기 때문)
  • validationQuery 생략: 최신 JDBC 드라이버는 isValid() API를 지원하므로, SELECT 1 같은 쿼리를 직접 작성할 필요가 없습니다. HikariCP는 드라이버 수준에서 유효성을 체크합니다.
  • maxLifetime의 중요성: DB의 wait_timeout보다 최소 30초 이상 짧게 설정해야 "죽은 커넥션"을 잡는 불상사를 막을 수 있습니다.

 

LIST

DB 선택은 시스템 설계에서 가장 중요한 의사결정 중 하나입니다.
오늘은 MariaDB, PostgreSQL, Oracle 세 가지 DB를
개발자 관점에서 명확하게 비교해보겠습니다.
특히 SI/기관/대규모 시스템 경험 기준으로 실무에서 어떤 차이가 나는지 집중해서 설명합니다.


1. 개요 – 각 DB의 포지션

✔ MariaDB

MySQL 기반의 오픈소스 DB.
가볍고 빠르고, 단순 CRUD 기반 서비스에 적합.
스타트업·내부관리·웹서비스에서 많이 사용.

✔ PostgreSQL

표준 SQL 준수율 최고 수준의 오픈소스 ORDBMS.
복잡한 쿼리·분석·JSON 처리·MVCC 안정성에 강함.
기관·SI·엔터프라이즈에서도 빠르게 확산 중.

✔ Oracle

대규모 금융/공공기관에서 사용하는 대표적인 상용 DB.
RAC, Undo 기반 MVCC, 강력한 안정성과 튜닝 기능 제공.
대형 미션 크리티컬 시스템에서 성능·안정성 최강자.


2. 구조적 특징 비교

🔥 MVCC(트랜잭션 동시성) 구조 차이

항목                             MariaDB(InnoDB)                             PostgreSQL                                    Oracle

 

MVCC 구현 Undo 로그 기반 테이블에 버전 쌓음 (WAL + Vacuum) Undo Tablespace 기반
충돌 처리 무난 안정적, 일관성 뛰어남 업계 최강
Vacuum 필요 없음 필요 (autovacuum) 없음

PostgreSQL과 Oracle은 MVCC가 매우 정교하며,
특히 Oracle은 Undo를 이용한 일관성 읽기(consistent read)가 완성도가 높습니다.


3. 성능 관점 비교

✔ 읽기/쓰기 패턴 기준

● MariaDB

  • 단일 INSERT/UPDATE 속도 빠름
  • 인덱스 튜닝 간단
  • 간단 CRUD 위주의 서비스에 적합
  • 대규모 조인은 상대적으로 약함

● PostgreSQL

  • 복잡한 조인, CTE, 윈도우 함수 성능 우수
  • JSONB 조회 성능 매우 강함
  • OLTP + 분석형 쿼리 혼합 업무에 적합
  • 대규모 데이터 처리에서도 안정적

● Oracle

  • 대규모 OLTP + 초고복잡 트랜잭션에서 독보적
  • RAC 등 멀티 노드 읽기/쓰기 처리 가능
  • 옵티마이저 성숙도 최상급
  • 비용 기반 최적화(CBO)가 뛰어남

4. SQL 기능 비교

기능MariaDBPostgreSQLOracle

 

SQL 표준 준수 보통 최고 매우 높음(단, Oracle 전용 문법 많음)
JSON 기능 있음 JSONB 최강 뒤늦게 지원 시작
윈도우 함수 중간 매우 강함 강함
CTE 기본 CTE 최적화 뛰어남 있음
GIS 기능 중간 PostGIS(최강) 있음
확장성 제한적 확장 모듈 다양 (TimescaleDB 등) 제한적 (상용 옵션)

PostgreSQL는 기능 범위가 오픈소스 중 가장 넓고,
Oracle은 기능이 많지만 벤더 종속성이 매우 강합니다.


5. 고가용성(HA)·복제 구조 비교

항목                             MariaDB(InnoDB)                             PostgreSQL                                    Oracle
기본 복제 Binlog WAL Redo/Archive
고가용성 옵션 Galera Cluster 강함 Patroni/Stolon 뛰어남 RAC(Active-Active)
복구 난이도 쉬움 중간 복잡(전문 DBA 필요)

Oracle RAC는 Active-Active 구조로
여러 노드에서 동시에 읽기·쓰기를 처리할 수 있어
초대형 시스템에서 압도적인 성능을 냅니다.


6. 운영 난이도·학습 난이도

✔ MariaDB

  • 설치·운영 쉬움
  • 튜닝 부담 적음
  • 소규모 웹서비스에 적합

✔ PostgreSQL

  • 안정적이나 개념량이 많아 학습 필요
  • Vacuum/Analyze 개념 필수
  • 중·대형 시스템에서 가장 적절한 선택지

✔ Oracle

  • PL/SQL, 파티션, RAC, Undo 등 구조 복잡
  • DBA 필수
  • 비용·운영 난이도 모두 높음
  • 하지만 안정성과 성능은 최상급

7. 비용(Budget) 비교

항목                             MariaDB(InnoDB)                             PostgreSQL                                    Oracle

 

라이선스 무료 무료 매우 고가 (Core 단위)
HA 옵션 무료 무료 별도 라이선스 (RAC 등)
유지보수 쉬움 중간 DBA 필요

Oracle은 비용 구조가 가장 무겁습니다.
공공/금융에서는 요구사항 때문에 어쩔 수 없이 선택하는 경우가 많습니다.


8. 어떤 상황에서 어떤 DB를 선택하면 좋을까?

✔ MariaDB가 좋은 경우

  • 스타트업, 중소규모 웹서비스
  • CRUD 중심 서비스
  • 운영·유지보수 비용 낮춰야 할 때
  • 단순한 트랜잭션 작업 위주

✔ PostgreSQL가 좋은 경우

  • SI/공공/기관 시스템 (조인·업무로직 복잡)
  • JSON + RDB 혼합 로직 필요
  • 통계·보고용 쿼리 혼합
  • 기능 완성도 높은 오픈소스 DB 원할 때
  • MySQL보다 더 강한 기능/안정성 필요할 때

✔ Oracle이 좋은 경우

  • 금융권·대규모 공공 시스템
  • 초고가용성 필요
  • 수백~수천 TPS 트랜잭션
  • RAC 같은 엔터프라이즈 기능 필요
  • DB 장애 허용 불가능한 환경

🔚 결론

각 DB는 분명한 포지션이 있습니다.

  • MariaDB → 가볍고 빠른 범용 웹서비스용
  • PostgreSQL → 기능·성능·표준 모두 강한 엔터프라이즈 오픈소스 DB
  • Oracle → 비용은 크지만 안정성과 기능성 최고인 상용 DB

개발자 입장에서
확장성과 기능, 성능, 실제 업무 구조까지 고려하면 PostgreSQL이 가장 균형 잡힌 선택지입니다.
특히 공공·기관 SI처럼 복잡한 데이터 모델을 다루는 환경에서는 PostgreSQL의 장점이 더 잘 드러납니다

LIST

MyBatis (2010년대 이후, 현재도 활발)

  • iBATIS 후속 프로젝트 (Apache → 개발자 커뮤니티 독립).
  • 철학: 여전히 “SQL은 내가 통제한다(SQL 중심)”이지만,
    • XML의 가독성 개선
    • 애노테이션 기반 Mapper 인터페이스 지원
    • CamelCase 매핑, 타입 핸들러 등 자동화 기능 강화
  • Spring과 통합이 잘 되어 있음 (MyBatis-Spring)
  • 대규모 트래픽 처리하는 전자상거래, 금융권, 공공 SI 등에서 여전히 많이 씀.

 

 

🔹 iBATIS 시대

  • iBATIS 1.x (2002~2004)
    • SQLMap 기반 단순 매핑 프레임워크.
    • Java 진영 외에도 .NET, Ruby 버전도 존재.
  • iBATIS 2.x (2004~2009)
    • XML 기반 매퍼 구조 정착.
    • 당시 국내 SI 업계에서 표준처럼 많이 사용됨.
    • 하지만 유지보수 한계 + JPA/Hibernate 부상.

🔹 MyBatis 시대 (iBATIS 후속)

  • 2010년: MyBatis 3.0 정식 출시
    • iBATIS 3.x로 계획됐으나, Apache에서 빠져나와 독립 프로젝트로 리브랜딩.
    • #{param} 문법, Mapper Interface + Annotation 지원, Spring 통합.
  • 3.1.x (2011~2012)
    • 애노테이션 매퍼 기능 강화.
    • 다양한 DB 지원 개선.
  • 3.2.x (2013~2014)
    • Java 8 호환성 강화.
    • TypeHandler 확장성 개선.
  • 3.3.x (2015~2016)
    • Stream, Lambda, Optional 같은 Java 8 기능과 궁합 맞춤.
    • Cache, Plugin API 안정화.
  • 3.4.x (2016~2018)
    • 성능 최적화, 로그 개선.
    • ResultSet 매핑 자동화 기능 확대.
  • 3.5.x (2019~2023)
    • Java 9~17 공식 지원.
    • JDK 모듈 시스템 호환.
    • MyBatis-Spring 2.x와 연동 최적화.
    • 점진적으로 버그픽스/보안패치 제공.
  • 3.6.x (2023~2024)
    • Java 21 지원 시작.
    • Kotlin, Spring Boot 3.x와 궁합 개선.
    • MyBatis Dynamic SQL, MyBatis-Kotlin 같은 서브 프로젝트 성장.
  • 3.7.x (2025 현재)
    • Java 22/23까지 호환성 대응 중.
    • Mapper XML/Annotation 혼합 사용 개선.
    • SQL 로깅/성능 모니터링 기능 강화.
    • 점점 “레거시 SI용”이 아니라, Spring Boot + JPA 보완재로 자리 잡는 중.

🔹 요약 타임라인

  • 2002~2009: iBATIS 1.x, 2.x (Apache)
  • 2010: MyBatis 3.0 (독립 프로젝트로 시작)
  • 2011~2018: 3.1 ~ 3.4 (Java 6~8 지원, 안정화 단계)
  • 2019~2023: 3.5 (Java 9~17, Spring Boot 2.x 호환)
  • 2023~2024: 3.6 (Java 21, Spring Boot 3.x 호환)
  • 2025~: 3.7 (최신 JDK 대응, 확장성 강화 중)

👉 정리하면:

  • **iBATIS → MyBatis 3.0 (2010)**에서 사실상 “새로운 프레임워크”로 거듭남.
  • 지금은 3.5.x~3.7.x 라인에서 최신 JDK + Spring Boot 3.x와 맞춰가는 게 핵심 트렌드입니다.
LIST

1. SQL 실행 자동화

  • MyBatis는 **SQL Mapper(XML, 어노테이션)**에 정의된 쿼리를 자동으로 실행해줍니다.
    → 예를 들어 insertUser(User user)를 매퍼에 정의하면, sqlSession.insert("namespace.insertUser", user) 형태로 호출만 하면 SQL이 실행됩니다.
  • 즉, JDBC처럼 직접 PreparedStatement를 만들고 바인딩할 필요 없이, MyBatis가 자동으로 처리해 줍니다.

 

 

MyBatis 3에서의 자동 매핑 동작

1. parameterType 생략 가능

  • parameterType은 메서드에 넘기는 파라미터 객체 타입을 명시하는 용도입니다.
  • 사실 생략해도 동작합니다.
    → MyBatis는 실행 시점에 넘어온 객체의 타입을 보고 처리하기 때문이에요.
  • 예시:
 
<select id="getUser" resultType="User"> SELECT * FROM user WHERE id = #{id} </select>

→ 여기서 parameterType="int"를 안 써도 #{id}로 잘 동작합니다.

다만 명시해주면 가독성·유지보수성이 좋아서 실무에서는 자주 씁니다.


2. resultType / resultMap 자동 매핑

  • resultType을 지정하면, 쿼리 결과의 컬럼명과 자바 객체의 필드명이 같으면 자동으로 매핑됩니다.
  • 예시:
 
<select id="getUser" parameterType="int" resultType="User"> SELECT id, name, email FROM user WHERE id = #{id} </select>

➡ DB 컬럼이 id, name, email이고, User 클래스에 동일한 필드가 있으면 자동으로 매핑됩니다.

  • 만약 DB 컬럼명이 user_id인데 객체 필드명이 id라면?
    → 자동 매핑이 안 되고, resultMap을 써야 합니다.
 
<resultMap id="userMap" type="User"> <id property="id" column="user_id"/> <result property="name" column="user_name"/> </resultMap>

➡ 그리고 resultMap="userMap"으로 지정.

 

3. 자동 매핑 단계 (MyBatis 내부 규칙)

  • MyBatis 3는 **자동 매핑 수준(autoMappingBehavior)**을 설정할 수 있습니다.
    • NONE: 자동 매핑 안 함
    • PARTIAL: 기본값, 컬럼명과 프로퍼티명이 같으면 매핑
    • FULL: 대소문자/언더스코어를 CamelCase까지 매핑 시도 (user_id → userId)

mybatis-config.xml 예시:

 
<settings> <setting name="autoMappingBehavior" value="FULL"/> </settings>

이렇게 하면 DB 컬럼이 user_id여도, Java의 userId에 자동으로 들어갑니다.


정리

  • parameterType: 안 써도 되지만, 문서화 차원에서 쓰는 게 권장.
  • resultType: 컬럼명 = 필드명이면 자동 매핑.
  • resultMap: 컬럼명 ≠ 필드명이면 수동 매핑 필요.
  • autoMappingBehavior를 FULL로 두면 CamelCase 변환까지 자동 매핑 지원

 

MyBatis 3에서 CamelCase 자동 매핑

MyBatis는 DB 컬럼명Java 객체 필드명이 다르더라도,
예를 들어

  • DB: user_id
  • Java: userId

이 경우도 자동 매핑이 가능합니다.


1. 설정 방법

mybatis-config.xml에서 다음 옵션을 켜주면 됩니다:

 
<settings> <!-- 컬럼명에 언더스코어(_) 있으면 CamelCase 프로퍼티로 자동 변환 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>

이걸 설정하면:

  • user_id → userId
  • user_name → userName
  • created_at → createdAt

처럼 자동 변환됩니다.


2. autoMappingBehavior

추가로 자동 매핑 동작 범위도 조정할 수 있습니다:

 
<settings> <setting name="autoMappingBehavior" value="FULL"/> </settings>
  • NONE: 자동 매핑 안 함
  • PARTIAL (기본): 단순히 이름이 같은 것만 매핑
  • FULL: 이름이 같거나 camelCase 매핑까지 폭넓게 지원

FULL + mapUnderscoreToCamelCase=true 조합이면 웬만한 건 다 자동 매핑돼서 resultMap 안 써도 됩니다.


3. 결론

  • CamelCase 자동 매핑 지원됨 → mapUnderscoreToCamelCase=true 필요.
  • parameterType은 생략 가능, MyBatis가 실행 시점에 객체 타입을 추론.
  • resultType은 꼭 지정해야 함, 다만 컬럼명 = 필드명(또는 CamelCase 규칙 적용)이면 자동 매핑.

 

LIST



MyBatis가 좋은 이유

1. SQL 제어권이 명확함
복잡한 쿼리, 조인, 인덱스 활용, 성능 튜닝 등을 개발자가 직접 세밀하게 제어할 수 있음. Query 플랜 보고 힌트 주거나, 특정 DB 기능 활용하기 좋음.


2. 동적 SQL 지원이 유연함
<if>, <choose>, <foreach> 같은 XML 기반 동적 SQL 요소가 있고, 파라미터 조합에 따라 SQL을 유연히 구성 가능.


3. 학습 진입 장벽이 낮은 편
SQL을 이미 잘 아는 개발자라면 “쿼리 작성 + 매핑” 구조가 익숙하고, JDBC 쪽 복잡한 boilerplate를 줄여주는 만큼 빠르게 사용할 수 있음.


4. DB 종속적인 기능 활용 가능
DB 벤더의 고유 기능(stored procedure, 함수, 힌트 등)을 쿼리에서 바로 쓸 수 있음. ORM보다 제약이 적음.


5. 명확한 매핑 & 코드 분리
SQL과 Java 코드를 분리(XML 또는 annotation), SQL 변경이 있을 때 Java 코드를 많이 건드리지 않아도 되는 구조 가능.




---

단점 / 제약

1. 반복 코드 & 매핑 유지 부담
CRUD 같은 단순 작업도 쿼리를 일일이 써야 하고, 테이블/컬럼 변경 시 매핑 XML이나 DTO/VO, Mapper 인터페이스 쪽도 같이 수정해야 함.


2. 런타임 에러 가능성
SQL 문법 오류, 컬럼 누락, 타입 매치 실패 등이 컴파일 타임에 잡히지 않고 실행 시점(runtime)에 발견되는 경우 많음.


3. DB 종속성 & 이식성 낮음
SQL 쿼리가 특정 DB 벤더 문법에 의존할 경우, DB 변경(예: MySQL → Oracle)시 손이 많이 감.


4. 객체 지향 기능 부족
1차 캐시, 쓰기 지연, 연관 관계 매핑(연관된 객체 간의 자동 fetch/join 등), 트랜잭션 경계 관리 등이 ORM (예: JPA/Hibernate) 만큼 자동화되어 있지 않아서, 이런 것들을 직접 고려해야 함.


5. 유지보수 측면의 복잡성
개발자 간 쿼리 작성 스타일이나 Mapper 구조가 잘 통일되지 않으면, 코드가 지저분해지거나 중복 많아져 버림. 헬퍼 매핑, 유틸, 공통 매개변수 처리 등이 잘 설계되어야 함.




---

ORM / JPA 대비 MyBatis가 나을/덜 나을 경우

상황 MyBatis가 유리한 경우 JPA/ORM 쪽이 유리한 경우

쿼리 복잡성 (조인 많음 / 서브쿼리 / DB 벤더 기능 활용) MyBatis
데이터 액세스 패턴이 단순 CRUD 중심, 도메인 간 관계 많은 객체 중심 모델 ORM
DB 변경 가능성 낮고 스키마 안정적인 경우 MyBatis
DB 종류 바꿀 가능성 있거나 벤더 독립성을 높여야 할 경우 ORM
퍼포먼스 미세 튜닝/쿼리 응답 시간 중요할 경우 MyBatis
생산성 / 유지보수 / 코드의 단순화 / 프로덕션에서 엔티티 기반 기능 활용(1차 캐시, lazy loading 등) 중요한 경우 ORM



---

MyBatis를 잘 쓰기 위한 고려 / 튜닝 포인트 (장애 예방 위주)

SQL 작성 시 인덱스, 데이터 양, JOIN 폭, 서브쿼리 vs 조인 vs 뷰 등을 충분히 고려

파라미터 바인딩 또는 프로젝션(projection) 최소화 (필요한 컬럼만 조회)

Mapper XML 구조 설계 시, 중복 쿼리, 공통 WHERE 절, 동적 SQL 패턴 추출 및 재사용

실행 계획(EXPLAIN), 쿼리 프로파일러 / APM 지표 활용

커넥션 풀 / 트랜잭션 경계 주의: MyBatis 쪽에서는 SqlSession 관리가 중요

페이징 처리는 limit/offset 또는 cursor, 필요한 경우 데이터 이동 전략 고려

캐시 활용: MyBatis 자체의 2차(cache) 설정 가능, 읽기 중심 쿼리에 대해 고려

테스트: SQL 변경 시 단위 테스트 또는 통합 테스트에서 쿼리 실행 확인



---

결론

MyBatis3는 매우 강력한 도구이고, 특히 다음 같은 프로젝트에서 좋은 선택이 될 가능성이 높습니다:

DB 중심 로직이 많거나 복잡한 쿼리를 많이 써야 하는 서비스

성능 및 응답시간 튜닝, 쿼리 제어가 중요한 경우

레거시 DB가 있고, 스키마나 테이블 설계가 자주 변경되지 않는 환경

벌크 작업, 보고서, 복잡 동적 쿼리, 최적화가 빈번한 경우


하지만, 객체 모델 중심의 개발, 스키마 변화 많음, 조직 규모 크고 유지보수자 많음, ORM의 편의성이 중요한 경우라면 MyBatis만 고집하는 것은 비효율일 수 있음. 실제로 많은 조직이 “기본 CRUD는 JPA/ORM + 복잡 쿼리/성능 중요 부분만 MyBatis 또는 네이티브 SQL” 혼합 전략을 쓰기도 하고요.




LIST

+ Recent posts