MyBatis는 JdbcTemplate보다 더 많은 기능을 제공하는 SQL 매퍼로, 동적 쿼리를 보다 편리하게 작성할 수 있는 장점이 있다. 쿼리를 XML 파일로 작성하기 때문에 코드가 길어지더라도 가독성이 좋고, 문자열을 더하는 등의 작업이 불필요하다.
public List<Item> findByCategory(String category) {
String sql = "SELECT * FROM items WHERE category = ?";
return jdbcTemplate.query(sql, new Object[]{category}, new BeanPropertyRowMapper<>(Item.class));
}
<!-- src/main/resources/mappers/ItemMapper.xml -->
<select id="findByCategory" resultType="Item">
SELECT * FROM items WHERE category = #{category}
</select>
public interface ItemMapper {
List<Item> findByCategory(@Param("category") String category);
}
MyBatis에서는 XML 파일에서 SQL을 작성하고, #{category}와 같이 파라미터 바인딩을 제공하여 깔끔하게 작성할 수 있다.
1. 의존성 추가
// build.gradle 예시
dependencies {
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.4'
}
2. 설정 파일 작성
application.yml 또는 application.properties에 MyBatis 설정을 추가한다.
mybatis:
type-aliases-package: hello.itemservice.domain
configuration:
map-underscore-to-camel-case: true
type-aliases-package: 특정 패키지의 클래스에 대해 별칭을 적용하여 XML에서 사용할 수 있게 한다.map-underscore-to-camel-case: 데이터베이스 필드의 스네이크 케이스를 Camel Case로 자동 매핑해준다.3. 인터페이스와 XML 파일 작성
인터페이스 파일을 생성하고, @Mapper 어노테이션으로 XML 파일과 연동한다.
예를 들어 ItemMapper 인터페이스와 매칭되는 ItemMapper.xml을 생성하여 사용한다.
@Mapper
public interface ItemMapper {
Item findById(@Param("id") Long id);
List<Item> findAll();
}
<!-- src/main/resources/mappers/ItemMapper.xml -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="hello.itemservice.repository.ItemMapper">
<select id="findById" parameterType="long" resultType="Item">
SELECT * FROM items WHERE id = #{id}
</select>
<select id="findAll" resultType="Item">
SELECT * FROM items
</select>
</mapper>
XML 파일에서 <, >와 같은 특수문자를 직접 사용할 수 없기 때문에, <, > 와 같이 대체 문자를 사용해야 한다.
XML 문서의 루트 엘리먼트로 <mapper>를 선언하고, namespace에 해당 매퍼 인터페이스의 패키지 경로를 설정해 MyBatis가 이를 참조할 수 있도록 한다.
MyBatis는 <if>, <choose>, <where> 등의 태그를 제공하여 동적으로 SQL을 구성할 수 있다.
<select id="findItems" resultType="Item">
SELECT * FROM items
<where>
<if test="category != null">
AND category = #{category}
</if>
<if test="price != null">
AND price <= #{price}
</if>
</where>
</select>
이러한 기능을 통해 조건에 따라 쿼리의 일부를 동적으로 추가할 수 있으며, 반복적인 쿼리 작성을 줄일 수 있다.
자세한 문법은 공식 사이트를 참고하자
@Mapper 어노테이션이 붙은 인터페이스가 구현체 없이 빈으로 등록되고 실행되는 이유는 MyBatis의 Mapper 프록시(Proxy) 메커니즘 덕분이다.
프록시 객체 생성
Spring Boot가 애플리케이션을 실행할 때 @Mapper 어노테이션이 붙은 인터페이스를 검색하고, 이를 바탕으로 MyBatis가 내부적으로 프록시 객체를 생성한다.
MyBatis-Spring 통합 동작
MyBatis와 Spring은 MapperScannerConfigurer 또는 @MapperScan을 통해 @Mapper 인터페이스를 찾아 빈으로 등록한다.
Spring의 AOP 기반 프록시 생성
Spring은 Java의 Proxy 클래스를 사용해 런타임에 동적으로 객체를 생성한다.
@Mapper 인터페이스를 기반으로 동적 프록시를 생성해, 메서드 호출을 MyBatis 실행 로직으로 위임한다.
MyBatis의 SQL 매퍼 연결
MyBatis는 XML 매퍼나 어노테이션 기반 SQL을 통해 실제 데이터베이스와 상호작용한다.
프록시는 이를 호출하기만 하므로 구현체가 없어도 동작한다.