데이터 접근 기술 (3) - MyBatis
개요
이번 글은 데이터 접근 기술 시리즈의 세 번째로, MyBatis에 대한 글이다.
김영한 님의 스프링 DB 강의와 개인 공부를 통해 학습한 내용을 정리하고자 한다.
Spring Boot 3.2.2, JDK 17 버전이며 H2를 데이터베이스로 사용하니 참고하자.
- 데이터 접근 기술 (1) - 데이터 접근 기술의 종류
- 데이터 접근 기술 (2) - JdbcTemplate
- 데이터 접근 기술 (3) - MyBatis
- 데이터 접근 기술 (4) - JPA
- 데이터 접근 기술 (5) - Spring Data JPA
- 데이터 접근 기술 (6) - Querydsl
MyBatis란?
데이터베이스 접근과 상호작용을 단순화하는 오픈 소스 프레임워크로, xml 기반의 sql 매핑을 지원한다.
특히 동적으로 sql을 생성할 수 있기 때문에 복잡한 쿼리의 작성이나 필요에 따라 동적으로 변경할 수 있는 유연성이 장점이다.
이전 글에서 설명한 JdbcTemplate보다 더 많은 기능을 지원하지만 오픈 소스이기 때문에 MyBatis를 사용하려면 좀 더 많은 설정이 필요하다.
MyBatis의 주요 특징은 다음과 같다.
- sql 매핑 파일(xml)을 사용하여 데이터베이스 쿼리 정의
- 동적 sql 생성 가능
- 데이터베이스 테이블과 자바 객체 간의 필드 매핑 설정 가능
- 자동 트랜잭션, 커넥션 등 관리
기본 설정
1. build.gradle
dependencies {
//MyBatis 사용
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
//H2 데이터베이스 사용
runtimeOnly 'com.h2database:h2'
}
MyBatis를 사용하기 위한 설정을 추가한다. 이 때 버전은 스프링 부트의 버전과 맞춰야 한다.
나는 앞서 언급했듯 스프링 부트 3.2.2를 사용하기 때문에 3.0.3을 추가했다.
https://start.spring.io/에서 프로젝트를 생성한다면 Dependencies에 MyBatis를 추가하여 버전에 맞는 라이브러리를 자동으로 등록할 수 있다.
📌 참고
H2 데이터베이스 설치 및 실행 방법은 아래의 글을 참고하자.
https://hyunrian.tistory.com/88
Mac에서 H2 설치 및 실행하기
H2 Database는 간단한 개발이나 테스트 용도로 사용하기 좋은 DB이다. 이번 글에서는 맥에서 H2를 설치하고 실행하는 과정을 진행해보려 한다. H2 설치하기 아래의 링크로 들어가면 최신 버전을 OS에
hyunrian.tistory.com
2. application.properties
#DB 연결
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
#MyBatis 관련 설정(편의)
mybatis.type-aliases-package=review.data.domain
mybatis.mapper-locations=classpath:mapper/**/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.review.data.repository.mybatis=trace
DB 연결 설정은 필수지만 MyBatis 관련 설정은 편의를 위한 것이니 선택적으로 작성하면 된다.
- `mybatis.type-aliases-package`
- xml 파일에서 resultType의 패키지명을 생략하여 작성할 수 있음
- 지정한 패키지를 포함하여 그 하위 패키지까지 자동으로 인식
- `mybatis.mapper-locations`
- sql을 작성할 xml 파일의 경로는 기본적으로 아래에서 나올 Mapper 인터페이스와 동일한 경로에 위치해야 하며, 이 경우 별도의 설정이 필요하지 않음
- 인터페이스와 다른 경로에 위치할 경우 해당 디렉토리 경로를 작성
- `classpath:mapper/**/*.xml` : resources/mapper를 포함하여 그 하위 디렉토리 내 xml 파일 전체를 인식
- `mybatis.configuration.map-underscore-to-camel-case=true`
- 데이터베이스 컬럼명이 `user_name`이고 객체의 필드명이 `userName`인 경우 자동으로 변경됨
- `logging.level.hello.itemservice.repository.mybatis=trace`
- MyBatis에서 실행되는 쿼리 로그 확인 가능
코드 작성
간단한 CRUD 기능을 구현할 것이며 작성한 클래스 및 파일은 아래와 같다.
- `User` : 도메인 객체. 아이디(pk), 유저명, 핸드폰 번호를 필드로 가짐
- `UserMapper` : xml을 호출하는 매퍼 인터페이스
- `UserRepository` : 리포지토리 인터페이스
- `MyBatisUserRepository` : 리포지토리 구현체
- `UserSearchCondition` : 데이터 조회시의 검색어를 필드로 갖고 있는 검색 조건 객체
- `UserUpdateDto` : update에서 사용할 데이터 객체. `User` 대신 사용할 것
- `UserMapper.xml` : 실행할 sql이 있는 xml 파일
이 중에서 중요하지 않은 몇가지 클래스는 코드를 생략하겠다.
📌 참고
데이터베이스(H2)에 테이블은 아래와 같이 생성해 두었다.
create table users (
id bigint generated by default as identity,
user_name varchar(10),
phone_num varchar(25),
primary key (id)
);
1. `User`
@Data
@NoArgsConstructor
public class User {
private Long id;
private String userName;
private String phoneNum;
public User(String userName, String phoneNum) {
this.userName = userName;
this.phoneNum = phoneNum;
}
}
2. `UserMapper`
@Mapper
public interface UserMapper {
//회원 저장
void save(User user);
//회원 수정
void update(@Param("id") Long id, @Param("userDto") UserUpdateDto userDto);
//회원 아이디로 찾기
Optional<User> findById(Long id);
//회원 전체 조회. 검색어가 있는 경우 검색 결과에 반영
List<User> findAll(UserSearchCondition searchCondition);
//회원 삭제
void delete(Long id);
}
MyBatis Mapping xml을 호출해주는 인터페이스로, `@Mapper`를 반드시 붙여야 MyBatis가 인식할 수 있다.
xml로 넘길 파라미터가 두개 이상인 경우 `@Param`으로 파라미터명을 지정하도록 한다.
3. `UserMapper.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="review.data.repository.mybatis.UserMapper">
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into users (user_name, phone_num)
values (#{userName}, #{phoneNum})
</insert>
<update id="update">
update users
set user_name = #{userDto.userName},
phone_num = #{userDto.phoneNum}
where id = #{id}
</update>
<select id="findById" resultType="User">
select * from users
where id = #{id}
</select>
<select id="findAll" resultType="User">
select * from users
<if test="userName != null || userName != ''">
where user_name like concat('%', #{userName}, '%')
</if>
</select>
<delete id="delete">
delete from users
where id = #{id}
</delete>
</mapper>
- application.properties에서 `mybatis.mapper-locations`를 따로 지정하지 않았다면 위에서 작성한 `UserMapper` 인터페이스와 동일한 경로를 resources 디렉토리 하위에 생성하여 xml 파일을 위치하도록 한다.
- `<mapper></mapper>` 위의 내용은 기본 설정이니 반드시 작성해야 한다.
- `<mapper>`의 `namespace`는 인터페이스의 전체 경로를 지정한다.
- id는 Mapper 인터페이스에서 설정한 메서드명을 지정한다.
- `void save(User user);` 👉🏻 `<insert id="save">`
- UserMapper 인터페이스에서 넘어온 파라미터가 객체인 경우 #{필드명}으로 작성한다.
- 👉🏻 `#{userName}`
- UserMapper 인터페이스에서 넘어온 파라미터가 2개 이상이며 그 중 객체가 있을 경우 `@Param()`으로 지정한 파라미터명을 넣어 #{파라미터명.필드명}으로 작성한다.
- 👉🏻 `#{userDto.userName}`
- `userGeneratedKeys`는 DB가 키를 생성해주는 IDENTITY 전략일 때 사용하며 `keyProperty`는 해당 키의 컬럼명을 지정한다. insert가 완료되면 User 객체에 id로 생성된 값이 입력된다.
- `<select>`처럼 반환할 데이터가 있는 경우 resultType에 반환할 데이터의 타입을 명시해야 한다.
- application.properties에서 `mybatis.type-aliases-package`을 지정하지 않았다면 `resultType="review.data.domain.User"`와 같이 전체 경로를 포함하여 작성해야 한다.
- `<where>`, `<if>`와 같은 동적 쿼리 문법을 사용할 수 있다.
- `<if>` : 해당 조건에 따라 구문을 추가함
4. `MyBatisUserRepository`
@Repository
public class MyBatisUserRepository implements UserRepository {
@Autowired
UserMapper userMapper;
@Override
public User save(User user) {
userMapper.save(user);
return user;
}
@Override
public void update(Long id, UserUpdateDto userDto) {
userMapper.update(id, userDto);
}
@Override
public Optional<User> findById(Long id) {
return userMapper.findById(id);
}
@Override
public List<User> findAll(UserSearchCondition searchCondition) {
return userMapper.findAll(searchCondition);
}
@Override
public void delete(Long id) {
userMapper.delete(id);
}
}
`UserRepository` 인터페이스를 구현한 클래스로 `UserMapper` 인터페이스를 주입하여 로직을 수행한다.
`findById()`처럼 `UserMapper.xml`에서 반환하는 객체가 한개일 때는 `User`나 `Optional<User>`를 사용하고,
`findAll()`처럼 여러개일 때는 `List<User>`를 사용하면 된다.
모든 코드를 작성하고 테스트를 수행하면 모두 정상적으로 동작하는 것을 확인할 수 있다.
만약 오류가 발생한다면 설정이 잘못되었거나 sql 쿼리에 오타가 있을 확률이 높다.
MyBatis의 동적 쿼리
MyBatis가 동적 쿼리 작성을 위해 제공하는 기능으로는
- `<if>`
- `<choose> (when, otherwise)`
- `<trim> (where, set)`
- `<foreach>`
등이 있는데, `<where>`과 `<if>`를 함께 사용하는 경우에 대해 잠깐 살펴보겠다.
<select id="findAll" resultType="Book">
select * from books
<where>
<if test="state != null"> <!-- 1번 -->
state = #{state}
</if>
<if test="title != null"> <!-- 2번 -->
and title like #{title}
</if>
<if test="author != null and author.name != null"> <!-- 3번 -->
and author_name like #{author.name}
</if>
</where>
</select>
쿼리를 보면 `<if>`의 조건에 따라 문장을 추가하거나 추가하지 않거나 결정이 된다.
만약 여기서 1번 조건을 제외하고 2번 조건만 해당된다면
`select * from books where and title like ?`와 같은 쿼리가 생성될 것 같지만
`<where>` 내 문장이 and로 시작한다면 and를 지우고 쿼리를 생성하며, 문장이 없으면 where을 추가하지 않는 기능이 있다.
따라서 위 예시의 쿼리는 정상적으로 동작하게 될 것이다.
더 많고 자세한 내용은 MyBatis의 공식 문서를 참고하도록 하자.
https://mybatis.org/mybatis-3/ko/dynamic-sql.html
mybatis – 마이바티스 3 | 동적 SQL
동적 SQL 마이바티스의 가장 강력한 기능 중 하나는 동적 SQL을 처리하는 방법이다. JDBC나 다른 유사한 프레임워크를 사용해본 경험이 있다면 동적으로 SQL 을 구성하는 것이 얼마나 힘든 작업인지
mybatis.org
참고
- 스프링 DB 2편 - 데이터 접근 활용 기술 (김영한)
- https://mybatis.org/mybatis-3/getting-started.html
'Database' 카테고리의 다른 글
데이터 접근 기술 (6) - Querydsl (0) | 2024.01.04 |
---|---|
데이터 접근 기술 (5) - Spring Data JPA (0) | 2024.01.01 |
데이터 접근 기술 (4) - JPA (0) | 2023.12.18 |
데이터 접근 기술 (2) - JdbcTemplate (0) | 2023.12.04 |
데이터 접근 기술 (1) - 데이터 접근 기술의 종류 (0) | 2023.11.29 |
Liked this Posting!