복잡한뇌구조마냥

[Spring] MyBatis 구조 정리 본문

BE/Spring

[Spring] MyBatis 구조 정리

지금해냥 2025. 8. 29. 09:50

스프링 생태계에서는 JPA가 가장 널리 사용되지만, 여전히 명시적인 SQL 제어, 복잡한 조인/튜닝, DB 프로시저 실행 등이 필요한 경우에는 MyBatis가 훨씬 강력한 선택지가 된다.
이번 포스팅에서는 Spring Boot 프로젝트에서 MyBatis를 실제로 사용할 때 알아야 하는 핵심 요소들을 정리한다.

※ JPA와 혼용하는 환경에서도 동일하게 적용 가능함.


1. 왜 MyBatis를 사용하는가?

MyBatis를 선택하는 이유는 대부분 다음 세 가지다.

✔ 1) SQL을 직접 컨트롤하고 싶을 때

ORM은 객체 중심이지만 MyBatis는 SQL 중심 설계이므로
실제 DB에서 어떻게 동작하는지 완전히 통제할 수 있다.

✔ 2) 복잡한 조회 쿼리, 다중 조인, 서브쿼리 작성이 편함

JPA는 복잡한 조회 로직에서 한계를 드러내지만
MyBatis는 순수 SQL을 그대로 작성하므로 튜닝이 쉽다.

✔ 3) 성능 기반 프로젝트에서 자주 사용됨

고 트래픽의 시스템에서는 SQL 최적화가 중요하기 때문에
대규모 서비스(쇼핑몰, 금융사, 배치 시스템 등)에서는 여전히 많이 사용된다.


2. Spring Boot에서 MyBatis 기본 설정

Spring Boot + MyBatis 조합에서는 starter 의존성 하나면 거의 자동 설정된다.

📦 Gradle

implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.5")

📁 application.yml

mybatis:
  mapper-locations: classpath:mybatis/mapper/**/*Mapper.xml
  type-aliases-package: com.back.domain.**.dto

여기서 중요한 두 가지는:

옵션 설명
mapper-locations XML 매퍼 파일 경로
type-aliases-package VO/DTO 별칭 패키지 경로

스프링 부트는 이 설정을 기반으로 자동으로 SqlSessionFactory를 생성한다.


3. Mapper Interface + XML 구조

MyBatis의 구조는 아래 패턴으로 이루어진다.

Mapper Interface
 ↕
XML Mapper (SQL 작성)
 ↕
DB

예시: MemberMapper.java

 
@Mapper
public interface MemberMapper {

    Member findByEmail(String email);

    void saveMember(Member member);

    List<Member> findAll();
}

예시: MemberMapper.xml

 
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.back.domain.member.mapper.MemberMapper">

    <select id="findByEmail" resultType="Member">
        SELECT *
        FROM members
        WHERE email = #{email}
    </select>

    <insert id="saveMember">
        INSERT INTO members (email, password, nickname)
        VALUES (#{email}, #{password}, #{nickname})
    </insert>

    <select id="findAll" resultType="Member">
        SELECT *
        FROM members
    </select>

</mapper>

4. SQL 파라미터 규칙 정리

✔ 단일 파라미터

  • 그대로 #{param} 사용 가능.
WHERE id = #{id}

✔ DTO/VO 전달 시

  • 필드명을 그대로 쓰면 된다.
INSERT INTO table (name, age) VALUES (#{name}, #{age})

✔ 여러 파라미터 전달 시

  • @Param이 필요하다.
Member findByNickname(@Param("nickname") String nickname, @Param("age") int age);
  • XML:
WHERE nickname = #{nickname} AND age = #{age}

 


5. MyBatis의 강점인 동적 SQL(choose/if/where/trim)

MyBatis의 가장 큰 장점 중 하나가 바로 동적 SQL 컨트롤이다.
조건에 따라 쿼리를 조립할 수 있어 실무에서 조회 쿼리를 만들 때 매우 유용하다.

예시: 조건 기반 검색

<select id="searchMembers" resultType="Member">
    SELECT * FROM members
    <where>
        <if test="email != null">
            AND email = #{email}
        </if>
        <if test="nickname != null">
            AND nickname = #{nickname}
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
    </where>
</select>

이렇게 구성하면 null인 조건은 자동으로 제외되고, 필요한 조건만 안전하게 붙는다.


6. ResultMap — 컬럼 매핑을 정교하게 제어하기

MyBatis는 resultType 외에도 ResultMap으로 세밀한 매핑 규칙을 지정할 수 있다.

예시:

<resultMap id="MemberResult" type="Member">
    <id property="id" column="id"/>
    <result property="email" column="email"/>
    <result property="nickname" column="nickname"/>
    <result property="role" column="role"/>
</resultMap>

<select id="findById" resultMap="MemberResult">
    SELECT * FROM members WHERE id = #{id}
</select>

ResultMap은 조인 쿼리처럼 다중 매핑 또는 컬렉션 매핑이 필요한 상황에서 특히 강력하다.


7. MyBatis vs JPA 실무 비교

항목 MyBatis JPA
쿼리 제어 완전 수동 추상화 높음
복잡한 조회 매우 강함 QueryDSL 필요
개발 속도 SQL 작성 필요 엔티티 중심 개발
성능 튜닝 DB 기준 최적화 캐싱/지연 로딩 이점
실무 사용처 리포트, 관리자 시스템, 배치, 금융 대부분의 CRUD 서비스

❗ 결론:

“CRUD 중심 → JPA, 조회 중심 + 고성능 → MyBatis”
두 기술을 혼용하는 하이브리드 구조가 많이 쓰임.


8. 실무에서 자주 쓰는 패턴 정리

✔ 배치/대량 연산은 MyBatis가 압도적으로 유리

대량 insert/update 시 JPA는 매우 비효율적이다.

<insert id="bulkInsert">
    INSERT INTO logs (message, created_at)
    VALUES
    <foreach collection="list" item="log" separator=",">
        (#{log.message}, #{log.createdAt})
    </foreach>
</insert>

✔ 조인 결과를 계층 구조로 매핑

<resultMap id="PostWithImages" type="Post">
    <id column="post_id" property="id"/>
    <collection property="images" ofType="Image">
        <id column="image_id" property="id"/>
        <result column="url" property="url"/>
    </collection>
</resultMap>

9. MyBatis를 사용할 때 주의할 점

🚫 1) SQL은 항상 N+1을 직접 고려해야 한다

JPA는 자동으로 fetch join/tuning이 가능하지만, MyBatis에서는
쿼리를 명확히 작성하지 않으면 필드마다 쿼리가 또 날아가는 경우가 발생한다.

🚫 2) XML 관리 비용이 증가할 수 있음

Mapper 개수가 많아지면 폴더 구조와 네이밍 규칙이 중요하다.

🚫 3) 동적 SQL이 너무 복잡해지면 유지보수 어려움

적절한 레이어 분리가 필요.


📌 마무리

이번 포스팅에서는 Spring 기반에서 MyBatis를 사용하는 데 필요한 핵심 개념을 모두 정리해보았다.
MyBatis는 SQL을 명확하게 제어할 수 있다는 큰 장점이 있어 JPA와 함께 혼합 전략으로 쓰면 매우 강력한 조합이 된다.

 

참고자료:

https://github.com/Yoepee/mybatis-250827 (개인 레포 연습)

 

GitHub - Yoepee/mybatis-250827: 스프링부트, 마이바티스, 인터셉터

스프링부트, 마이바티스, 인터셉터. Contribute to Yoepee/mybatis-250827 development by creating an account on GitHub.

github.com

 

LIST