728x90
1. MyBatis 소개
- JdbcTemplate 보다 더 많은 기능을 제공하는 SQL Mapper
- JdbcTempate이 제공하는 대부분의 기능을 제공
- SQL을 XML에 편리하게 작성 및 동적 쿼리를 매우 편리하게 작성 가능
- project에서 동적 쿼리와 복잡한 쿼리가 많을 시 MyBatis를 사용하고, 단순 쿼리가 많을 시, JdbcTemplate을 선택해서 사용하면 된다.
- MyBatis 공식 사이트 : https://mybatis.org/mybatis-3/ko/index.html
- 현재 수행하는 MyBatis 방식은 spring boot에서 권장하는 방식
1.1. spring(maven)에서 spring boot(gradle)에서 Mybatis 사용방법 차이점
- spring - maven
- pom.xml
- mybatis : mybatis 자체 의존성 == db와 작업 하는 의존성
- mybatis-spring() : mybatis와 spring framework 연동 관리, transaction,session 관리
- bean 등록
- datasource 등록
- sqlSessionFactory 등록 : classpath:mapper/*Mapper.xml , classpath:mybatis-config.xml 관리
- sqlSession 등록 : 해당 bean을 이용해서 crud method 사용
- mybatis-config.xml 설정 파일
- typeAlias 등록에 사용 - Dto
- 핵심 code 및 파일
- dto 파일, repository 파일, Mapper.xml 파일 이용
- 내부적 서로 연결하는 nameSpace는 서로 동일하기만 하면 된다.
- repository 파일의 메서드 명이 곧 Mapper.xml의 id이다.
- pom.xml
- springboot - gradle
- build.gradle
- implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
- 위의 의존 관계 1개만 받으면 spring에서 받은 2개의 의존성을 다 수행한다.
cf> springboot만 쓰면 pom.xml도 mybatis-spring-boot-starter 이것만 받으면 된다.
- bean 등록
- datasource, sqlSessionFactory, sqlSession 자동 등록
- springboot는 @Mapper를 사용하기 때문에 sqlSession을 사용하지는 않지만 굳이 사용해서 spring 때처럼 코드 작성하려한 할 수 있다.
- mybatis-config.xml 설정 파일
- 자동 등록
- application.properties
- datasource, sqlSessionFactory, sqlSession, mybatis-config.xml 에서 수정하거나 추가하는 부분을 application.properties에 등록만 하면 boot가 알아서 해당 bean, config 파일에 값을 다 넣어준다.
- 참고 : test에 application.properties 파일이 존재시, 동일하게 설정 값을 넣어줘야 정상 작동
- 핵심 코드 및 파일
- dto 파일, @Mapper interface, Repository implements Mapper, Mapper.xml
- properties 파일로 Mapper.xml의 파일명과 경로는 동일하게 둘 수 있지만 Mapper.xml 내부 nameSpace의 value는 @Mapper interface의 절대경로를 정확히 작성해야함.
- 참고 : @Mapper interface는 단 하나의 Mapper.xml에 들어갈 수 있다. 유일해야한다.
- build.gradle
2. MyBatis 설정
- build.gradle
// build.gradle
plugins {
id 'org.springframework.boot' version '2.6.5'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
...
// JdbcTemplate 추가
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
// MyBatis 추가 - 김영한 강사님 file을 그대로 사용한 것이여서 2.2.0으로 작성해야한다.
// boot 3.0 이상 사용시 version을 3.0.1로 변경 해야한다.
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
// H2 데이터베이스 추가
runtimeOnly 'com.h2database:h2'
...
}
- application.properties
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
# password가 없는 경우 생략가능하지만 그냥 작성
spring.datasource.password=
# jdbcTemplate sql log 설정
logging.level.org.springframework.jdbc=debug
## MyBatis
# xml package명 생략 해줌, static import 같은 역할
mybatis.type-aliases-package=hello.itemservice.domain
# 관계형 불일치 해결 - default가 false ex)userItem == user_item
mybatis.configuration.map-underscore-to-camel-case=true
# log 찍으려고 일단 등록해둠
logging.level.hello.itemservice.repository.mybatis=trace
- 설정 설명
- mybatis.type-aliases-package : myBatis에서 type정보를 반환 혹은 매개변수로 보낼 경우 Mapper에서 package를 포함한 절대 경로가 필요한대, 이를 static import 처럼 생략할 수 있도록 해주는 과정
- mybatis.configuration.map-underscore-to-camel-case=true : java의 member와 db의 column 값의 camel-case와 underscore을 동일 취급
- logging.level.hello.itemservice.repository.mybatis=trace : trace 범위로 sql을 확인함
- mybatis.type-aliases-package : myBatis에서 type정보를 반환 혹은 매개변수로 보낼 경우 Mapper에서 package를 포함한 절대 경로가 필요한대, 이를 static import 처럼 생략할 수 있도록 해주는 과정
3. MyBatis 적용1 - 기본
JdbcTemplate과 비교
- JdbcTemplate
- 해당 repository interface를 구현하는 구현체로 바로 사용 - 내부에 sql logic, 기본적으로 제공받는 datasource, txManger 사용
- MyBatis
- Mapper interface 사용
- MyBatis가 Mapper 내부에 datasource, txManger를 넣어준다.
- Mapper interface에는 repository interface의 method만 존재
- Mapper interface와 동일 이름의 .xml 파일에 sql logic 작성
- resource 내부에 동일 경로에 생성후, xml 작성
- repository interface의 구현체는 method 구현부에 Mapper interface의 method를 넣어서 사용
- Mapper interface 사용
- code
3.1. MyBatis에서 사용하는 class
- item repository
package hello.itemservice.repository;
public interface ItemRepository {
Item save(Item item);
void update(Long itemId, ItemUpdateDto updateParam);
Optional<Item> findById(Long id);
List<Item> findAll(ItemSearchCond cond);
}
- ItemMapper
- 경로 : hello/itemservice/repository/mybatis/ItemMapper.java
- @Mapper : MyBatis가 인식 -> xml의 해당 SQL을 실행하고 결과 돌려준다.
package hello.itemservice.repository.mybatis;
@Mapper
public interface ItemMapper {
void save(Item item);
void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto updateParam);
Optional<Item> findById(Long id);
List<Item> findAll(ItemSearchCond itemSearch);
}
- ItemMapper.xml
- ItemMapper.xml의 경로(default)
- resource 패키지 내부에 현재 @Mapper와 동일한 경로와 동일한 명으로 파일을 생성
- resource와 java 패키지가 동일 한 경로에 있으므로 이거 이하부터 동일하게 작성하면 된다.
- src/main/resources/hello/itemservice/repository/mybatis/ItemMapper.xml
- resource와 java 패키지가 동일 한 경로에 있으므로 이거 이하부터 동일하게 작성하면 된다.
- ItemMapper.java와 동일한 이름
- namespcae 해당 @Mapper의 절대경로를 정확하게 기입
- 참고 : 경로 수정하는 방법
- XML 파일을 원하는 위치에 두고 싶으면 application.properties 에 다음과 같이 설정
mybatis.mapper-locations=classpath:mapper/**/*.xml [붉은 글씨가 변경 포인트] - 이렇게 하면 resources/mapper 를 포함한 그 하위 폴더에 있는 XML을 XML 매핑 파일로 인식
이 경우 파일 이름은 자유롭게 설정해도 된다. - 참고: test/application.properties 파일도 함께 수정해야 테스트를 실행할 때 인식
- XML 파일을 원하는 위치에 두고 싶으면 application.properties 에 다음과 같이 설정
- ItemMapper.xml의 경로(default)
<?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">
<!--namespace : @Mapper가 존재하는 interface path-->
<mapper namespace="hello.itemservice.repository.mybatis.ItemMapper">
<!--useGenerateKeys : db에서 생성하는 key 사용, keyProperty : db키를 id라는 column에 사용-->
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into item(item_name, price, quantity)
values (#{itemName}, #{price}, #{quantity})
<!-- 넘어오는 params가 1가지 일경우 class명 제외한 iv명만 작성 가능 -->
</insert>
<update id="update">
update item
<!-- 넘어오는 Param가 2개일 경우 class명을 같이 작성 가능 -->
set item_name=#{updateParam.itemName},
price=#{updateParam.price},
quantity=#{updateParam.quantity}
where id = #{id} -- id Params 는 기본형이여서 그냥 사용
</update>
<!--resultType : ResultSet -> object로 변환
application.properties 에서 package 경로를 static import처럼 등록했기 때문에 class 명만 사용 가능
-->
<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>
- 사용 문법
- <insert>
- #{} : params 문법 / PreparedStatement 사용 / JDBC ' ? ' 를 치환한다고 생각
- userGeneratedKeys: db가 생성해주는 key
- KeyProperty: 생성되는 키의 속성 이름
- <update>
- 파라미터가 1개만 있는 경우, @Param을 지정하지 않아도 되지만 2개 이상인 경우 @Param으로 이름을 지정해서 params를 구분해야한다
- 파라미터가 1개만 있는 경우, @Param을 지정하지 않아도 되지만 2개 이상인 경우 @Param으로 이름을 지정해서 params를 구분해야한다
- <select>
- RetrunType: ResultSet -> Item 객체에 매핑
- properties 설정으로
1) package경로.Item을 Item으로 사용
2) 카멜 케이스와 언더스코어 케이스 관계 불일치 해결 - 반환 객체가 2개 이상일 경우 list로 반환
- 동적 queary
- MyBatis의 최대 장점. 동적 쿼리 및 아주 복잡한 쿼리를 직관적으로 작성할 수 있도록 돕는다.
- <where> : 만족하는 if 없을시 제거, if가 하나라도 성공시 처음 나타나는 and를 where로 변환
- <if> : where 조건에서 사용
- <insert>
4. MyBatis 적용2 - 설정과 실행
- MyBatisItemRepository
package hello.itemservice.repository.mybatis;
@Repository
@RequiredArgsConstructor
public class MyBatisItemRepository implements ItemRepository {
// 구현체는 @Mapping interface + xml 읽어서 합친거 들어옴
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);
}
}
- MyBatisConfig
package hello.itemservice.config;
@Configuration
@RequiredArgsConstructor
public class MyBatisConfig {
// MyBatis module이 datasource, txManger를 읽어서 itemMapper에 연결해줌
private final ItemMapper itemMapper;
@Bean
public ItemService itemService() {
return new ItemServiceV1(itemRepository());
}
@Bean
public ItemRepository itemRepository() {
return new MyBatisItemRepository(itemMapper);
}
}
- ItemServiceApplication
package hello.itemservice;
@Slf4j
@Import(MyBatisConfig.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ItemServiceApplication.class, args);
}
// application.properties의 설정 중 local 환경일 경우 @Bean으로 등록한다.
@Bean
@Profile("local")
public TestDataInit testDataInit(ItemRepository itemRepository) {
return new TestDataInit(itemRepository);
}
}
5. MyBatis 적용3 - 분석
- Mapper 구현체
- myBatis 스프링 연동 모듈이 만들어주는 구현체 덕분에 interface만으로 편리하게 xml data를 찾아서 호출 가능
- 예외 변환 까지 처리 : DataAccessException에 맞게 변환해서 반환해준다. = JdbcTemplate이 제공하는 예외변환기능을 여기서도 제공한다고 이해
6. MyBatis 기능 정리1 - 동적 쿼리
- MyBatis 공식 메뉴얼: https://mybatis.org/mybatis-3/ko/index.html
- MyBatis 스프링 공식 메뉴얼: https://mybatis.org/spring/ko/index.html
- 동적 SQL - 핵심 명사
- if
- choose(when, otherwise)
- tring(where, set)
- foreach
- 동적 쿼리 자세한 내용 : https://mybatis.org/mybatis-3/ko/dynamic-sql.html
7. MyBatis 기능 정리2 - 기타 기능
- <include> : 재사용 가능한 SQL 조각
- <resultMap> : as로 관계형 불일치 해결하는 방법 외의 방법
- <association> <collection> : 복잡한 결과 매핑