Spring/[인프런 김영한 스프링 DB 2편 - 데이터 접근 활용 기술]
[인프런 김영한 스프링 DB 2편 - 데이터 접근 활용 기술] 데이터 접근 기술 - MyBatis
h2boom
2024. 11. 21. 18:57
MyBatis
MyBatis 소개
- MyBatis : JdbcTemplate보다 많은 기능을 제공하는 SQL Mapper이다.
- SQL을 XML에 편리하게 작성가능
- 동적쿼리를 매우 편리하게 작성 가능
- MyBatis는 별도의 설정이 약간은 필요하다.
- ibatis는 MyBatis의 이전 버전이다.
<update id="update">
update item
set item_name=#{itemName},
price=#{price},
quantity=#{quantity}
where id = #{id}
</update>
- MyBatis XML로 SQL 작성 예제
- 쿼리가 길어져도 편리하게 작성할 수 있다.
<select id="findAll" resultType="Item">
select id, item_name, price, quantity
from item
<where>
<if test="itemName != null and itemName != ''">
and item_name like concat('%',#{itemName},'%')
</if>
<if test="maxPrice != null">
and price <= #{maxPrice}
</if>
</where>
</select>
- MyBatis 동적 쿼리 예제
- 동적 쿼리를 편리하게 작성할 수 있는 다양한 기능 제공
- 프로젝트에서 동적 쿼리와 복잡한 쿼리가 많다면 => MyBatis
단순한 쿼리가 많다면 JdbcTemplate을 선택해서 사용하면 된다.
MyBatis 설정
//MyBatis 추가
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
- JdbcTemplate이나 H2 Database 같이 스프링 부트가 버전을 관리해주는 공식 라이브러리의 경우 뒤에 버전 정보가 붙지 않지만 공식 라이브러리가 아닌 MyBatis의 경우 build.gradle에 라이브러리 추가 시 버전 정보를 붙여줘야 한다.
- 공식 라이브러리의 경우 버전 정보를 별도로 붙이지 않아도 스프링 부트에서 최적의 버전을 자동으로 찾아준다.
#MyBatis
mybatis.type-aliases-package=hello.itemservice.domain
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.hello.itemservice.repository.mybatis=trace
- application.properties MyBatis 설정
- type-aliases~ : MyBatis에서 타입 정보를 사용할 때 패키지 이름을 적어줘야하는데 application.properties에 명시해놓으면 패키지 명을 생략할 수 있다.
- 지정한 패키지, 하위 패키지까지 자동으로 인식된다.
- 여러 위치를 지정하려면 , ;로 구분
- configuration.~ : 언더스코어 표기를 카멜케이스로 자동 변경해주는 기능
- logging.~ : 해당 패키지에서 실행되는 쿼리 로그 확인
- 테스트 케이스에도 적용하려면 테스트의 application.properties에도 설정해줘야 한다.
- type-aliases~ : MyBatis에서 타입 정보를 사용할 때 패키지 이름을 적어줘야하는데 application.properties에 명시해놓으면 패키지 명을 생략할 수 있다.
MyBatis 적용
@Mapper
public interface ItemMapper {
void save(Item item);
void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto updateParam);
List<Item> findAll(ItemSearchCond itemSearch);
Optional<Item> findById(Long id);
}
- MyBatis 매핑 XML을 호출해주는 Mapper 인터페이스 예제 코드
- Mapper 인터페이스에는 @Mapper 어노테이션을 붙여줘야 MyBatis가 인식한다.
- 인터페이스의 메소드를 호출하면 xml의 해당 SQL을 실행 후 결과를 반환해준다.
<?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="hello.itemservice.repository.mybatis.ItemMapper">
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into item (item_name, price, quantity)
values (#{itemName}, #{price}, #{quantity})
</insert>
<update id="update">
update item
set item_name=#{updateParam.itemName},
price=#{updateParam.price},
quantity=#{updateParam.quantity}
where id = #{id}
</update>
<select id="findById" resultType="Item">
select id, item_name, price, quantity
from item
where id = #{id}
</select>
<select id="findAll" resultType="Item">
select id, item_name, price, quantity
from item
<where>
<if test="itemName != null and itemName != ''">
and item_name like concat('%',#{itemName},'%')
</if>
<if test="maxPrice != null">
and price <= #{maxPrice}
</if>
</where>
</select>
</mapper>
- XML 매핑 파일 예제 코드
- Mapper 인터페이스와 패키지 위치를 동일하게 맞춰야 한다.
- ex) Mapper 인터페이스 위치 : /hello/itemservice/repository/mybatis 의 경우
XML 매핑 파일 위치 : src/main/resources/hello/itemservice/repository/mybatis로 해줘야한다. - 만약 XML 파일을 원하는 위치에 두고 싶은 경우
- application.properties에서 mybatis.mapper-locations=classpath:mapper/**/*.xml 설정
=> resources/mapper를 포함한 하위 폴더에 있는 XML을 XML 매핑 파일로 인식한다.
- application.properties에서 mybatis.mapper-locations=classpath:mapper/**/*.xml 설정
- ex) Mapper 인터페이스 위치 : /hello/itemservice/repository/mybatis 의 경우
- namespace : Mapper 인터페이스를 지정해줘야 한다.
- save
- Insert SQL은 <insert> 사용한다.
- id에는 Mapper 인터페이스에 선언한 메소드 이름을 지정한다.
- 파라미터는 #{} 문법을 사용하며 Mapper에서 넘긴 객체의 프로퍼티(필드) 명을 적는다.
- #{} 문법 사용 시 PreparedStatement를 사용한다.
=> JDBC의 ?를 치환하는 개념
- #{} 문법 사용 시 PreparedStatement를 사용한다.
- useGeneratedKeys는 DB가 PK를 생성해주는 IDENTITY 전략일 때 사용한다.
- keyProperty는 생성되는 키의 필드 명을 지정한다.
- insert가 끝난 후 객체의 필드에 생성된 값이 입력된다.
- update
- Update SQL은 <update>를 사용한다.
- Mapper에서 파라미터가 2개 이상인 경우 @Param으로 이름을 지정해서 구분해줘야 한다.
- findById
- Select SQL은 <select>를 사용한다.
- resultType은 반환 타입을 명시하면 된다.
- JdbcTemplate의 BeanPropertyRowMapper처럼 SELECT SQL의 결과를 편리하게 객체로 바로 변환해준다.
- 자바 코드에서 반환 객체가 하나인 경우 Item, Optional<Item> 과 같이 사용하고 하나 이상인 경우 컬렉션 List를 주로 사용한다.
- Select SQL은 <select>를 사용한다.
- findAll
- <where>, <if> 같은 동적 쿼리 문법을 통해 편리한 동적 쿼리를 지원한다.
- <if>는 해당 조건이 만족하면 구문을 추가한다.
- <where>은 적절하게 where 문장을 만들어준다.
- 예제에서 <if>가 모두 실패하면 where를 만들지 않고 하나라도 성공하면 처음 나타나는 and를 where로 변환해준다.
- XML에서 <, >와 같은 특수 무자를 사용할 수 없기에 <는 <로 치환해줬다.
- CDATA 구문 문법을 사용할 수지만 XML TAG가 단순 문자로 인식되기에 상황에 맞게 알맞은 방법을 사용하자.
- <where>, <if> 같은 동적 쿼리 문법을 통해 편리한 동적 쿼리를 지원한다.
- Mapper 인터페이스와 패키지 위치를 동일하게 맞춰야 한다.
@Repository
@RequiredArgsConstructor
public class MyBatisItemRepository implements ItemRepository {
private final ItemMapper itemMapper;
@Override
public Item save(Item item) {
itemMapper.save(item);
return item;
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
itemMapper.update(itemId, updateParam);
}
@Override
public Optional<Item> findById(Long id) {
return itemMapper.findById(id);
}
@Override
public List<Item> findAll(ItemSearchCond cond) {
return itemMapper.findAll(cond);
}
}
- MyBatis를 사용한 Repository 예제 코드
- MyBatis 모듈이 트랜잭션 매니저, 데이터소스를 읽어서 Mapper와 자동으로 연결시켜주기에 별도로 데이터소스를 주입받지 않아도 된다.
- Mapper 인터페이스의 구현체가 없는데 동작하는 이유
- 애플리케이션 로딩 시점에 MyBatis 스프링 연동 모듈은 @Mapper가 붙어있는 인터페이스를 찾는다.
- 해당 인터페이스를 찾으면 동적 프록시 기술을 사용해서 Mapper 인터페이스의 구현체를 만든다.
- 생성된 구현체를 스프링 빈으로 등록한다.
- Mapper 구현체
- MyBatis 스프링 연동 모듈이 만들어주는 Mapper 구현체 덕분에 인터페이스만으로 편리하게 XML의 데이터를 찾아서 호출할 수 있다.
- Mapper 구현체는 예외 변환까지 처리해준다.
- 스프링 예외 추상화인 DataAccessException에 맞게 변환해서 반환해준다.
- MyBatis 스프링 연동 모듈이 DB 커넥션, 트랜잭션과 관련된 기능도 MyBatis와 함께 연동하고 동기화해준다.
MyBatis 기능 정리 - 동적 SQL
- 동적 SQL
- if
- choose (when, otherwise)
- trim
- foreach
<select id="findActiveBlogWithTitleLike" resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
- 동적 쿼리 if 예제
- 해당 조건에 따라 값을 추가할지 말지 판단한다. (내부적인 문법으로 OGNL을 사용한다.)
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
- 동적 쿼리 choose, when, otherwise 예제
- 자바의 switch 구문과 유사하다.
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
- 동적 쿼리 where 예제
- <where>는 문장이 없으면 where를 추가하지 않고 문장이 있으면 where를 추가한다.
- and가 먼저 시작되면 and를 지운다.
- <where>는 문장이 없으면 where를 추가하지 않고 문장이 있으면 where를 추가한다.
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
- 동적 쿼리 trim 예제
- <where>와 같은 기능을 수행한다.
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
- 동적 쿼리 foreach 예제
- 컬렉션을 반복 처리할 때 사용한다.
- where in (1,2,3,4,5,6)과 같은 문장을 쉽게 완성할 수 있다.
- 파라미터로 List를 전달하면 된다.
- 컬렉션을 반복 처리할 때 사용한다.
MyBatis 기능 정리 - 기타 기능
@Select("select id, item_name, price, quantity from item where id=#{id}")
Optional<Item> findById(Long id);
- 어노테이션으로 SQL 작성 예제
- XML 대신 어노테이션에 SQL을 작성할 수 있다.
- @Insert, @Update, @Delete, @Select 기능이 제공된다.
- XML 내용과 겹치면 오류가 생기므로 하나만 사용해야한다.
- 동적 SQL이 해결되지 않기에 간단한 경우에만 사용한다.
- XML 대신 어노테이션에 SQL을 작성할 수 있다.
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
- 문자열 대체 예제
- #{} 문법은 ?를 넣고 파라미터를 바인딩하는 PreparedStatement 방식을 사용한다.
- 파라미터 바인딩이 아닌 문자 그대로 처리하고 싶은 경우에는 ${}를 사용하면 된다.
- ${}는 SQL 인젝션 공격을 당할 수 있기에 가급적 사용하면 안된다.
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
<sql id="sometable">
${prefix}Table
</sql>
<sql id="someinclude">
from
<include refid="${include_target}"/>
</sql>
<select id="select" resultType="map">
select
field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
- SQL 재사용 예제
- <sql> 사용 시 SQL 코드를 재사용할 수 있다.
- <include>를 통해서 <sql> 조각을 찾아서 사용할 수 있다.
- include 시에 프로퍼티 값을 전달하고 내부에서 사용할 수 있다.
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{id}
</select>
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
- Result Maps 예제
- 기본적으로 결과를 매핑할 때 테이블과 객체명이 다르면 별칭(as)를 사용해야 한다.
- as를 사용하지 않고 resultMap을 사용할 수도 있다.
- property : 객체명
- column : 테이블 컬럼명
- <association>, <collection> 등을 사용하면 MyBatis도 매우 복잡한 결과에 객체 연관관계를 고려해서 데이터를 조회할 수 있다.
정리
- MyBatis 장점
- SQL을 XML로 작성 가능해서 SQL을 여러 줄을 작성할 때 편리하다.
- 동적 쿼리를 작성하기에 편리하다.
- 프로젝트에서 동적 쿼리와 복잡한 쿼리가 많다면 => MyBatis를 사용하고
단순한 쿼리가 많다면 JdbcTemplate을 선택해서 사용하면 된다.
- MyBatis Mapper 인터페이스
- Mapper 인터페이스에는 @Mapper 어노테이션을 붙여줘야 MyBatis가 인식한다.
- 인터페이스의 메소드를 호출하면 xml의 해당 SQL을 실행 후 결과를 반환해준다.
- MyBatis 스프링 연동 모듈이 Mapper 인터페이스의 구현체를 자동으로 만들어서 스프링 빈으로 등록해준다.
- XML 매핑 파일
- Mapper 인터페이스와 패키지 위치를 동일하게 맞춰야 한다.
- namespace는 Mapper 인터페이스를 지정한다.
- 파라미터는 #{} 문법을 사용하며 Mapper에서 넘긴 객체의 프로퍼티(필드) 명을 적는다.
- Mapper에서 파라미터가 2개 이상인 경우 @Param으로 이름을 지정해서 구분해줘야 한다.
- <insert>, <select>, <update> 등을 지원한다.
- id 값은 Mapper 인터페이스의 메소드 명으로 작성한다.
- <if>, <where> 등을 사용하면 편리하게 동적 쿼리를 작성할 수 있다.
출처 : [인프런 김영한 스프링 DB 2편 - 데이터 접근 활용 기술]
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard
스프링 DB 2편 - 데이터 접근 활용 기술 강의 | 김영한 - 인프런
김영한 | 백엔드 개발에 필요한 DB 데이터 접근 기술을 활용하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 백엔드
www.inflearn.com