MyBatis

2025. 5. 3. 23:01·BackEnd/Spring Framework

Spring Framework와 함께 데이터 접근 계층을 구현하는 데 널리 사용되는 MyBatis에 대해 알아보겠습니다. 데이터베이스와의 상호작용은 거의 모든 웹 애플리케이션에서 필수적인데, Spring과 MyBatis를 함께 사용하면 이 작업을 훨씬 효율적으로 처리할 수 있습니다.

전통적인 JDBC의 한계

먼저 전통적인 JDBC 접근 방식의 문제점을 살펴보겠습니다. 다음은 회원 등록을 처리하는 일반적인 JDBC 코드입니다.

@Override
public int registMember(Member member) throws SQLException {
    Connection con = util.getConnection();
    try {
        con.setAutoCommit(false);
        int result = mDao.insert(con, member); // 실제 비즈니스 로직
        con.commit();
        return result;
    } catch (SQLException e) {
        con.rollback();
        throw e;
    } finally {
        util.close(con);
    }
}

@Override
public int insert(Connection con, Member member) throws SQLException {
    String sql = "insert into member (name, email, password) values(?,?,?)";
    int result = -1;
    try (PreparedStatement pstmt = con.prepareStatement(sql)) {
        pstmt.setString(1, member.getName());
        pstmt.setString(2, member.getEmail());
        pstmt.setString(3, member.getPassword());
        result = pstmt.executeUpdate(); // 실제 쿼리 실행
    }
    return result;
}

이 코드에는 다음과 같은 문제점이 있습니다

  1. 기술 종속적인 코드: 비즈니스 로직보다 기술적인 코드(연결 관리, 트랜잭션 처리 등)가 더 많습니다.
  2. 반복적인 작업: 모든 데이터베이스 작업마다 유사한 패턴의 코드를 반복해야 합니다.
  3. 리소스 관리: 연결, 문장, 결과셋 등의 리소스를 직접 관리해야 하는 부담이 있습니다.
  4. 예외 처리 복잡성: 체크 예외 처리를 위한 try-catch 블록이 필요합니다.
  5. SQL과 Java 코드의 혼재: SQL 쿼리가 Java 코드 안에 문자열로 존재하여 유지보수가 어렵습니다.

MyBatis란?

MyBatis는 이러한 문제를 해결하기 위한 퍼시스턴스 프레임워크입니다. 주요 특징은 다음과 같습니다.

  1. SQL과 Java 코드의 분리: SQL 쿼리를 XML 파일로 분리하여 관리할 수 있습니다.
  2. 자동 객체 매핑: ResultSet 결과를 Java 객체에 자동으로 매핑합니다.
  3. 간소화된 JDBC 코드: 연결 관리, 파라미터 설정, 결과 매핑 등을 자동화합니다.
  4. 동적 SQL 지원: 조건에 따라 SQL을 동적으로 구성할 수 있습니다.
  5. Spring과의 통합: Spring과 쉽게 통합되어 트랜잭션 관리 등을 Spring에 위임할 수 있습니다.

Spring Boot에서 MyBatis 설정하기

Spring Boot 환경에서 MyBatis를 사용하기 위한 설정을 단계별로 살펴보겠습니다.

1. 의존성 추가

Maven pom.xml에 다음 의존성을 추가합니다.

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

<!-- 데이터베이스 드라이버 (MySQL 예시) -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

2. application.properties 설정

# 데이터소스 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=password

# 커넥션 풀 설정
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.minimum-idle=3
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.connection-timeout=600000

# MyBatis 설정
mybatis.type-aliases-package=com.example.domain
mybatis.mapper-locations=classpath:/mappers/**/*.xml

여기서 주요 설정은 다음과 같습니다.

  • type-aliases-package: DTO 클래스가 위치한 패키지를 지정합니다. 이렇게 하면 XML 매퍼에서 전체 경로 대신 클래스명만으로 참조할 수 있습니다.
  • mapper-locations: SQL 매퍼 XML 파일의 위치를 지정합니다. 위 설정은 classpath의 mappers 디렉토리와 그 하위 모든 디렉토리에서 XML 파일을 찾습니다.

3. DAO(Data Access Object) 인터페이스 작성

public interface MemberDao {
    // 회원 등록
    int insert(Member member);
    
    // 이메일로 회원 조회
    Member select(String email);
    
    // 모든 회원 조회
    List<Member> search();
}

MyBatis를 사용하면 DAO 인터페이스만 정의하고 구현 클래스는 작성할 필요가 없습니다. MyBatis가 런타임에 구현체를 동적으로 생성해 줍니다. 또한 전통적인 JDBC에서와 달리 Connection 객체를 메서드 인자로 전달할 필요가 없습니다.

4. XML 매퍼 작성

다음으로 SQL 쿼리를 정의할 XML 매퍼 파일을 작성합니다. 이 파일은 src/main/resources/mappers 디렉토리에 생성합니다.

<?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.example.dao.MemberDao">
    <!-- 회원 등록 -->
    <insert id="insert" parameterType="Member" useGeneratedKeys="true" keyProperty="mno">
        INSERT INTO member (name, email, password) 
        VALUES (#{name}, #{email}, #{password})
    </insert>
    
    <!-- 이메일로 회원 조회 -->
    <select id="select" parameterType="String" resultType="Member">
        SELECT mno, name, email, password 
        FROM member 
        WHERE email = #{email}
    </select>
    
    <!-- 모든 회원 조회 -->
    <select id="search" resultType="Member">
        SELECT mno, name, email, password 
        FROM member
    </select>
</mapper>

XML 매퍼 파일의 주요 요소를 살펴보겠습니다.

  • namespace: DAO 인터페이스의 전체 경로와 일치시켜야 합니다. MyBatis는 이를 통해 인터페이스와 XML을 매핑합니다.
  • id: DAO 인터페이스의 메서드명과 일치시켜야 합니다.
  • parameterType: 파라미터의 타입을 지정합니다. type-aliases 설정을 통해 패키지 없이 클래스명만으로 참조할 수 있습니다.
  • resultType: 조회 결과를 매핑할 객체의 타입을 지정합니다.
  • useGeneratedKeys, keyProperty: 자동 생성 키(auto increment)를 사용할 때 지정합니다. 생성된 키 값이 keyProperty에 지정된 객체 속성에 자동으로 할당됩니다.

또한 SQL 쿼리에서 파라미터를 참조할 때는 #{속성명} 형식을 사용합니다. 이는 PreparedStatement의 '?'로 치환되어 SQL 인젝션을 방지합니다.

5. 매퍼 스캔 설정

마지막으로 Spring Boot 애플리케이션 클래스에 MyBatis 매퍼를 스캔하도록 설정합니다.

@SpringBootApplication
@MapperScan("com.example.dao")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@MapperScan 어노테이션은 지정된 패키지에서 MyBatis 매퍼 인터페이스를 찾아 자동으로 등록합니다. 이로써 DAO 인터페이스에 @Mapper 어노테이션을 일일이 붙이지 않아도 됩니다.

서비스 계층 구현하기

MyBatis와 Spring을 함께 사용할 때 서비스 계층은 다음과 같이 구현할 수 있습니다.

@Service
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {
    
    private final MemberDao memberDao;
    
    @Transactional
    @Override
    public int registerMember(Member member) {
        return memberDao.insert(member);
    }
    
    @Override
    public Member getMember(String email) {
        return memberDao.select(email);
    }
    
    @Override
    public List<Member> getAllMembers() {
        return memberDao.search();
    }
}

여기서 주목할 점은 다음과 같습니다.

  1. 생성자 주입: @RequiredArgsConstructor와 final 필드를 통해 의존성을 주입받습니다.
  2. 트랜잭션 관리: @Transactional 어노테이션을 통해 선언적으로 트랜잭션을 관리합니다. 이제 트랜잭션 시작, 커밋, 롤백을 직접 코드로 작성할 필요가 없습니다.
  3. 예외 처리 단순화: MyBatis와 Spring은 JDBC 예외를 Spring의 DataAccessException으로 변환하여 체크 예외를 처리해야 하는 번거로움을 줄여줍니다.

ResultMap을 이용한 고급 매핑

때로는 데이터베이스 컬럼명과 Java 객체의 속성명이 다르거나, 객체 간의 관계를 매핑해야 할 필요가 있습니다. 이런 경우 ResultMap을 사용할 수 있습니다.

<resultMap id="memberMap" type="Member">
    <id property="mno" column="mno"/>
    <result property="name" column="member_name"/>
    <result property="email" column="member_email"/>
    <result property="password" column="member_password"/>
</resultMap>

<select id="select" resultMap="memberMap">
    SELECT mno, name AS member_name, email AS member_email, password AS member_password 
    FROM member 
    WHERE email = #{email}
</select>

ResultMap의 주요 요소는 다음과 같습니다.

  • id: 매핑을 식별하는 고유 이름
  • type: 결과를 매핑할 객체 타입
  • id 태그: 기본 키 컬럼을 매핑
  • result 태그: 일반 컬럼을 매핑
    • property: Java 객체의 속성명
    • column: 데이터베이스 컬럼명

객체 간 관계 매핑

MyBatis는 객체 간의 일대일(has-one), 일대다(has-many) 관계도 매핑할 수 있습니다.

일대일 관계 매핑 (has-one)

회원과 프로필처럼 일대일 관계를 매핑하려면 association 태그를 사용합니다.

<resultMap id="memberWithProfileMap" type="Member">
    <id property="mno" column="mno"/>
    <result property="name" column="name"/>
    <result property="email" column="email"/>
    <association property="profile" javaType="Profile">
        <id property="profileId" column="profile_id"/>
        <result property="pictureUrl" column="picture_url"/>
        <result property="bio" column="bio"/>
    </association>
</resultMap>

<select id="selectWithProfile" resultMap="memberWithProfileMap">
    SELECT m.mno, m.name, m.email, p.profile_id, p.picture_url, p.bio
    FROM member m
    LEFT JOIN profile p ON m.mno = p.mno
    WHERE m.email = #{email}
</select>

일대다 관계 매핑 (has-many)

회원과 주소처럼 일대다 관계를 매핑하려면 collection 태그를 사용합니다.

<resultMap id="memberWithAddressesMap" type="Member">
    <id property="mno" column="mno"/>
    <result property="name" column="name"/>
    <result property="email" column="email"/>
    <collection property="addresses" ofType="Address">
        <id property="ano" column="ano"/>
        <result property="title" column="title"/>
        <result property="address" column="address"/>
        <result property="detailAddress" column="detail_address"/>
    </collection>
</resultMap>

<select id="selectWithAddresses" resultMap="memberWithAddressesMap">
    SELECT m.mno, m.name, m.email, a.ano, a.title, a.address, a.detail_address
    FROM member m
    LEFT JOIN address a ON m.mno = a.mno
    WHERE m.email = #{email}
</select>

이 외에도 다양한 매핑 방법이 있습니다.

  1. 중첩 Select: 주 쿼리 실행 후 추가 쿼리를 실행하여 관련 객체를 로드
  2. 중첩 ResultMap: 다른 ResultMap을 참조하여 재사용
  3. 컬럼 접두사: 컬럼명이 중복될 때 접두사를 사용하여 구분

Spring과 MyBatis 통합의 장점

Spring과 MyBatis를 함께 사용할 때의 주요 장점은 다음과 같습니다.

  1. 선언적 트랜잭션 관리: @Transactional 어노테이션으로 간편하게 트랜잭션 관리
  2. 통합된 예외 처리: MyBatis 예외가 Spring의 DataAccessException으로 변환되어 일관된 예외 처리 가능
  3. 의존성 주입: Spring의 IoC 컨테이너를 통해 DAO에 대한 의존성 주입 관리
  4. 테스트 용이성: 단위 테스트와 통합 테스트가 더 쉬워짐
  5. AOP 활용: 로깅, 성능 측정 등을 위한 관점 지향 프로그래밍 적용 가능

마무리

이번 포스팅에서는 Spring Framework와 MyBatis를 통합하여 사용하는 방법을 알아보았습니다. MyBatis는 SQL과 Java 객체 간의 매핑을 쉽게 해주며, Spring과 함께 사용하면 많은 보일러플레이트 코드를 제거할 수 있습니다. SQL을 직접 작성할 수 있어 복잡한 쿼리에 대한 제어력을 유지하면서도, 많은 반복 코드를 제거해주어 개발 생산성을 높여줍니다. 또한 Spring과의 통합을 통해 트랜잭션 관리, 예외 처리 등 다양한 기능을 손쉽게 활용할 수 있습니다.

'BackEnd > Spring Framework' 카테고리의 다른 글

Spring의 트랜잭션 관리  (1) 2025.05.30
MyBatis의 동적 SQL  (0) 2025.05.23
Interceptor | ErrorPage | FileUpload  (0) 2025.04.30
Spring MVC  (0) 2025.04.27
Spring AOP  (0) 2025.04.26
'BackEnd/Spring Framework' 카테고리의 다른 글
  • Spring의 트랜잭션 관리
  • MyBatis의 동적 SQL
  • Interceptor | ErrorPage | FileUpload
  • Spring MVC
leve68
leve68
leve68 님의 블로그 입니다.
  • leve68
    leve68
    leve68
  • 전체
    오늘
    어제
    • 분류 전체보기
      • BackEnd
        • Spring Framework
        • Database
      • FrontEnd
        • JavaScript
        • Vue
      • Infra
        • Docker
        • CI CD
      • CS
        • Algorithm
      • Project
        • Web
  • 인기 글

  • 태그

    sql
    MySQL
    spring security
    Spring
    MyBatis
    springboot
    SSAFY
    docker
    DATABASE
    compose
  • 링크

    • github
    • portfolio
  • 최근 글

  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.3
leve68
MyBatis
상단으로

티스토리툴바