1. Spring-MyBatis
앞선 글에서는 MyBatis를 이해하기 위해 jar파일로 임포트해서 사용하는 기초적인 방법을 사용했지만 실제 프로젝트에서는 대부분 스프링 부트에 얹어서 함께 사용한다. 스프링부트에서는 Mybatis starter를 지원한다. https://start.spring.io 나 IDE에서 스프링부트 프로젝트를 만들 수 있다.
MyBatis가 스프링을 만나면 훨씬 더 강력해진다. 우선 첫번째. DB와 상호작용이 필요할 때 SqlSessionFactory로부터 SqlSession을 만드는 과정을 스프링이 대신해준다. 두번째. SqlSession에서 매핑된 SQL 쿼리를 가져와서 자바 인터페이스로 활용하는 과정도 스프링이 대신해준다. 즉, 자바 인터페이스가 스프링 빈으로 등록되어 있기 때문에 그냥 필요한 곳에 가져와서 쓰기만 하면 된다. 아래 그림을 보자.
MyBatis만 사용하는 그림과 비교해보면 SqlSession을 만드는 과정이 크게 압축되었다. 개발자가 적어주어야 했던 번거로운 과정의 대부분을 MyBatis-Spring이 대신해준다. 달라진 특징을 알아보자.
Mybatis-Spring 라이브러리의 시작인 SqlSessionFactoryBean은 기존의 SqlSessionFactoryBuilder를 대신해 SqlSessionFactory를 만들어 SqlSessionTemplate에 주입한다.
다음으로 SqlSessionTemplate은 기존의 두 단계를 합친 SqlSessionFactory + SqlSession의 역할을 하며, 트랜잭션 관리, 자원 관리, 스레드 안전성, 간편한 DAO 구현을 가능하게 한다. 따라서 개발자는 세션 관리에 신경 쓸 필요없이 비즈니스 로직에 집중할 수 있다.
2. 사용하기
Spring boot에서 의존성으로 MyBatis Framework와 H2 Database, Lombok을 추가시켜 주었다. 전체적인 프로젝트 구조는 다음과 같다.
스프링부트에서 MyBatis Framework를 추가하면, mybatis-spring-boot-starter를 통해 필요한 추가적인 라이브러리를 추가한다. mybatis-spring-boot-starter는 다음과 같은 장점을 가지고 있다.
- DataSource를 자동으로 탐지
- SqlSessionFactoryBean에 input으로 DataSource를 넣어 SqlSessionFactory 인스턴스를 만들고 스프링 빈으로 등록
- SqlSessionFactory로부터 SqlSessionTemplate 인스턴스를 만들고 스프링 빈으로 등록
- 개발자가 만든 mapper들을 자동으로 스캔하고 SqlSessionTemplate과 연결시킨 뒤 스프링 컨텍스트에 등록
따라서 mapper들이 스프링 빈으로 주입될 수 있다.
가장 많이 사용하는 일반적이고 효과적인 방법은 @MapperScan 어노테이션을 사용하는 것이다. 스프링 설정파일이 적절한 정보를 넣어주고 어노테이션을 사용하기만 하면 MyBatis가 알아서 설정 정보를 주입해서 Mapper를 바로 쓸 수 있도록 만들어 준다. 나는 프로젝트에 MyBatisConfig라는 이름의 클래스를 만들었다.
package com.churnobyl.mybatisdemo.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan(basePackages = {"com.churnobyl.mybatisdemo.model.dao"})
public class MyBatisConfig {
}
이렇게만 만들어주면 된다. 간단한 과정을 읊어보면, Spring이 시작될 때 @Configuration 어노테이션이 붙은 클래스들을 우선적으로 탐색해 필요한 정보들을 수집한다. 이때 Mybatis-spring의 @MapperScan 어노테이션이 있다면, basePackages의 경로 아래의 모든 interface를 탐색해 Mapper를 찾아 스프링 컨텍스트에 등록시켜 스프링이 빈으로 등록할 수 있도록 한다. 이때 DataSource는 매핑 정보는 스프링의 설정 파일에서 찾아간다. application.yml을 보자.
spring:
application:
name: mybatisdemo
datasource:
dbcp2:
driver: org.h2.Driver
url: jdbc:h2:tcp://localhost/~/mybatisdb
username: sa
password:
sql:
init:
mode: always
schema-locations: classpath*:schema.sql
h2:
console:
enabled: true
server:
port: 8090
mybatis:
mapper-locations: classpath:mappers/*.xml
type-aliases-package: com.churnobyl.mybatisdemo.model.dto
configuration:
map-underscore-to-camel-case: true
이런 식으로 DB와 연결하기 위한 DataSource나 mybatis에 관련한 정보를 넣어주면 된다.
mybatis.mapper-locations은 인터페이스와 매핑될 xml파일이 위치한 폴더를 지정해주는 것으로, 매퍼 xml파일은 주로 하나 이상이므로 다음과 같이 asterisk(*)을 이용해 전체를 스캔할 수 있도록 한다.
mybatis.type-aliases-package는 MyBatis가 사용자가 지정한 커스텀한 DTO를 이해하기 위해 필요한 패키지를 지정해주는 것이다.
마지막으로 mybatis.configuration.map-underscore-to-camel-case는 자바와 데이터베이스 간의 차이를 해소시켜주기 위한 설정으로, 일반적으로 자바 변수명은 Camel Case를 사용하는 반면 데이터베이스의 칼럼은 언더스코어를 사용하는 Snake case를 사용한다. 위 설정을 적용하면, SQL문에서 언더스코어를 작성한 칼럼명이 MyBatis를 통해 리턴될 때 CamelCase로 자동으로 매핑시킨다. 예를 들어 SELECT member_name, member_age FROM member; 라는 SQL문이 있다면 실제로 리턴되는 이름은 memberName, memberAge이 되는 것이다.
그럼 만약 MapperScan을 사용하지 않는다면 어떻게 될까.
당연히 MyBatis-Spring이 자동으로 해주는 동작을 직접 구현해주어야 한다. 다음은 MapperScan을 풀어 쓴 코드다.
package com.churnobyl.mybatisdemo.config;
import com.churnobyl.mybatisdemo.model.dao.MemberDao;
import com.churnobyl.mybatisdemo.model.dao.TeamDao;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setTypeAliasesPackage("com.churnobyl.mybatisdemo.model.dto");
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mappers/*.xml"));
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
public MapperFactoryBean<MemberDao> memberDao(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<MemberDao> factoryBean = new MapperFactoryBean<>(MemberDao.class);
factoryBean.setSqlSessionFactory(sqlSessionFactory);
return factoryBean;
}
@Bean
public MapperFactoryBean<TeamDao> teamDao(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<TeamDao> factoryBean = new MapperFactoryBean<>(TeamDao.class);
factoryBean.setSqlSessionFactory(sqlSessionFactory);
return factoryBean;
}
}
DataSource는 Spring이 자동으로 설정 정보를 Bean으로 등록해주지만 나머지는 수동으로 구현해야 한다. SqlSessionFactoryBean에 필요한 정보를 직접 넣어주고 SqlSessionFactory, SqlSessionTemplate, 그리고 각 Mapper를 수동으로 등록해주어야 한다.
MapperScan을 적극 활용하자.
자바 인터페이스와 mapper.xml
MemberDao
package com.churnobyl.mybatisdemo.model.dao;
import com.churnobyl.mybatisdemo.model.dto.member.request.AddMemberRequestDto;
import com.churnobyl.mybatisdemo.model.dto.member.request.LoginMemberRequestDto;
import com.churnobyl.mybatisdemo.model.dto.member.response.MemberResponseDto;
import org.apache.ibatis.annotations.Delete;
import java.util.List;
public interface MemberDao {
int signup(AddMemberRequestDto request);
int login(LoginMemberRequestDto request);
List<MemberResponseDto> getAll();
@Delete(value = "DELETE FROM member WHERE id = #{id}")
int delete(Long id);
}
memberMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.churnobyl.mybatisdemo.model.dao.MemberDao">
<!-- 유저 회원가입 -->
<insert id="signup" parameterType="addMemberRequestDto">
INSERT INTO member
(team_id, name, password)
VALUES
(#{teamId}, #{name}, #{password})
</insert>
<!-- 유저 로그인 -->
<select id="login" parameterType="loginMemberRequestDto" resultType="integer">
SELECT count(id) FROM member
WHERE name = #{name} and password = #{password}
</select>
<!-- 유저 모두 가져오기 -->
<select id="getAll" resultType="memberResponseDto">
SELECT
m.id as id,
t.name as teamName,
m.name as name
FROM member m
LEFT JOIN team t
ON m.team_id = t.id
</select>
</mapper>
SQL문을 자바의 메서드처럼 사용하기 위해서는 다음과 같이 코드를 작성하면 된다.
전체 프로젝트는 깃허브로 남겨두었다.
https://github.com/Churnobyl/mybatisdemo-tistory.git
'프로그래밍 > Java' 카테고리의 다른 글
[Java DB] 2-1. MyBatis - SQL 쿼리를 재사용하자 (개념편) (0) | 2024.06.12 |
---|---|
[Java DB] 1. JDBC - Java와 DB 상호작용의 기초 (0) | 2024.06.10 |
[Java] JDK 부수기 - (3) java.lang.Math (0) | 2023.12.20 |
[Java] JDK 부수기 - (4) java.lang.ClassLoader (0) | 2023.12.13 |
[Java] JDK 부수기 - (3) java.lang.Class (0) | 2023.12.04 |