framework/spring

4. 데이터 접근 기술 - MyBatis(springboot, gradle)

wooweee 2023. 9. 29. 03:13
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이다.

 

  • 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에 들어갈 수 있다. 유일해야한다.

 

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을 확인함

 

3. MyBatis 적용1 - 기본

 

JdbcTemplate과 비교

  • JdbcTemplate
    • 해당 repository interface를 구현하는 구현체로 바로 사용 - 내부에 sql logic, 기본적으로 제공받는 datasource, txManger 사용
  • MyBatis
    1. Mapper interface 사용
      • MyBatis가 Mapper 내부에 datasource, txManger를 넣어준다.
      • Mapper interface에는  repository interface의 method만 존재
    2. Mapper interface와 동일 이름의 .xml 파일에 sql logic 작성
      • resource 내부에 동일 경로에 생성후, xml 작성
    3. repository interface의 구현체는 method 구현부에 Mapper interface의 method를 넣어서 사용

 

  • 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
    • ItemMapper.java와 동일한 이름
    • namespcae 해당 @Mapper의 절대경로를 정확하게 기입
    • 참고 : 경로 수정하는 방법
      • XML 파일을 원하는 위치에 두고 싶으면  application.properties 에 다음과 같이 설정
        mybatis.mapper-locations=classpath:mapper/**/*.xml  [붉은 글씨가 변경 포인트]

      • 이렇게 하면  resources/mapper 를 포함한 그 하위 폴더에 있는 XML을 XML 매핑 파일로 인식
        이 경우 파일 이름은 자유롭게 설정해도 된다.

      • 참고: test/application.properties  파일도 함께 수정해야 테스트를 실행할 때 인식

 

<?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 &lt;= #{maxPrice}
            </if>
        </where>
    </select>
</mapper>

 

  • 사용 문법
    • <insert>
      • #{} : params 문법 / PreparedStatement 사용 / JDBC ' ? ' 를 치환한다고 생각
      • userGeneratedKeys: db가 생성해주는 key
      • KeyProperty: 생성되는 키의 속성 이름

    • <update>
      • 파라미터가 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 조건에서 사용

 

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 - 동적 쿼리

 

 

7.  MyBatis 기능 정리2 - 기타 기능

 

  • <include> : 재사용 가능한 SQL 조각
  • <resultMap> : as로 관계형 불일치 해결하는 방법 외의 방법
  • <association> <collection> : 복잡한 결과 매핑