MyBatis를 활용한 데이터베이스 CRUD 구현

MyBatis 개요

MyBatis는 JDBC의 복잡한 과정을 추상화하여 데이터베이스 연동을 단순화하는 ORM 프레임워크입니다. @Mapper 어노테이션이 붙은 인터페이스는 SQL 매퍼로 인식되며, 애플리케이션과 데이터베이스 간의 원활한 통신을 담당합니다.

프로젝트 환경 설정

의존성 추가

빌드 도구에 다음 의존성을 포함합니다:

  • mybatis-spring-boot-starter
  • mysql-connector-java

데이터소스 구성

application.yml 파일에 데이터베이스 접속 정보를 기술합니다.

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/sample_db?characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Seoul
    username: app_user
    password: secure_pass
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mybatis/mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true

로그 출력 설정

SQL 실행 내역을 콘솔에서 확인하려면 log-implStdOutImpl로 지정합니다.

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

매개변수 전달 방식

기본 바인딩

메서드의 인자를 SQL 내 #{파라미터명} 구문으로 참조합니다.

@Mapper
public interface MemberMapper {
    
    @Select("SELECT member_id, nickname, birth_year, email " +
            "FROM members WHERE birth_year = #{year}")
    List<Member> findByBirthYear(Integer year);
}

@Param을 이용한 별칭 지정

복수 파라미터 사용 시 @Param으로 명시적 이름을 부여하면 가독성이 향상됩니다.

@Mapper
public interface MemberMapper {
    
    @Select("SELECT member_id, nickname, birth_year, email " +
            "FROM members WHERE nickname = #{nick} AND status = #{status}")
    List<Member> findByNicknameAndStatus(@Param("nick") String nickname, 
                                          @Param("status") MemberStatus status);
}

테스트 코드:

@SpringBootTest
class MemberMapperTests {
    
    @Autowired
    private MemberMapper memberMapper;

    @Test
    void findByNicknameAndStatus() {
        List<Member> result = memberMapper.findByNicknameAndStatus("alice", MemberStatus.ACTIVE);
        result.forEach(System.out::println);
    }
}

데이터 조작 연산

레코드 삽입

@Mapper
public interface MemberMapper {
    
    @Insert("INSERT INTO members(nickname, encrypted_pwd, birth_year, gender, email, created_at) " +
            "VALUES(#{nickname}, #{encryptedPwd}, #{birthYear}, #{gender}, #{email}, NOW())")
    @Options(useGeneratedKeys = true, keyProperty = "memberId")
    int create(Member member);
}
@SpringBootTest
class MemberMapperTests {
    
    @Autowired
    private MemberMapper memberMapper;

    @Test
    void create() {
        Member newbie = new Member();
        newbie.setNickname("bob");
        newbie.setEncryptedPwd("hashed_value");
        newbie.setBirthYear(1995);
        newbie.setGender(1);
        newbie.setEmail("bob@example.com");
        
        int affectedRows = memberMapper.create(newbie);
        System.out.println("생성된 회원 번호: " + newbie.getMemberId());
    }
}

레코드 삭제

@Mapper
public interface MemberMapper {
    
    @Delete("DELETE FROM members WHERE member_id = #{memberId}")
    int remove(Long memberId);
}
@Test
void remove() {
    int deletedCount = memberMapper.remove(3L);
    System.out.println("삭제 건수: " + deletedCount);
}

레코드 갱신

@Mapper
public interface MemberMapper {
    
    @Update("UPDATE members SET nickname = #{nickname} WHERE member_id = #{memberId}")
    int modifyNickname(@Param("memberId") Long id, @Param("nickname") String newNickname);
}

레코드 조회

조회 시 컬럼명과 자바 필드명이 다르면 매핑이 누락될 수 있습니다. 예를 들어 created_at 컬럼은 createdAt 필드에 자동으로 매핑되지 않을 수 있습니다.

해결책 1: 별칭 사용

@Select("SELECT member_id, nickname, encrypted_pwd, birth_year, gender, " +
        "email, created_at AS createdAt, updated_at AS updatedAt " +
        "FROM members")
List<Member> findAllWithAlias();

해결책 2: @Results 매

@Select("SELECT member_id, nickname, encrypted_pwd, birth_year, gender, " +
        "email, created_at, updated_at FROM members")
@Results(id = "memberMap", value = {
    @Result(column = "member_id", property = "memberId"),
    @Result(column = "encrypted_pwd", property = "encryptedPwd"),
    @Result(column = "birth_year", property = "birthYear"),
    @Result(column = "created_at", property = "createdAt"),
    @Result(column = "updated_at", property = "updatedAt")
})
List<Member> findAllWithResultMap();

해결책 3: 전역 설정 (권장)

yml에서 map-underscore-to-camel-case: true를 활성화하면 스네이크 케이스 컬럼명이 자동으로 카멜 케이스 속성명으로 변환됩니다.

mybatis:
  configuration:
    map-underscore-to-camel-case: true

데이터베이스 설계 규약

  • 컬럼명은 소문자와 밑줄(_)로 구성
  • 모든 테이블은 id(자동 증가), created_at, updated_at 필수 포함

태그: MyBatis Spring Boot ORM JDBC SQL Mapper

6월 24일 20:14에 게시됨