| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 타입스크립트
- es6
- ES5
- 컴포넌튼
- 클론코딩
- entity
- TS
- &연산
- 당근마켓
- angular
- 서버리스 #
- Websocket
- TypeScript
- 0.75px border
- npm
- literal
- font-size
- 으
- 0.5px border
- github
- 1px border
- 10px
- 문서번호
- Props
- 데이터베이스 #try #이중
- ZOOM
- Strict
- jwt
- 전역변수
- 0.25px border
- Today
- Total
복잡한뇌구조마냥
[Spring] MyBatis 구조 정리 본문
스프링 생태계에서는 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
'BE > Spring' 카테고리의 다른 글
| [Spring] Spring Security + OAuth2 로그인 (0) | 2025.10.15 |
|---|---|
| [Spring] JPA + QueryDSL 정리 (0) | 2025.10.02 |
| [Spring] Spring Security + JWT 인증 구조 (0) | 2025.08.28 |
| [Spring] 스프링 부트 생성 (1) | 2025.06.18 |
| [Spring + JPA] 인프런 김영한님 커리큘럼 (2) | 2025.06.18 |