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-impl을 StdOutImpl로 지정합니다.
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필수 포함