Programming/Spring

Spring - 데이터 접근 기술 - MYBatis

잇(IT) 2023. 7. 4. 17:22

- JdbcTemplate과 비교해서 MyBatis의 장은 SQL을 XML에 편리하게 작성할 수 있고 또 동적 쿼리를 편리하게 작성할 수 있다.

 

- JdbcTemplate - SQL 여러줄

String sql = "update item " +
 			"set item_name=:itemName, price=:price, quantity=:quantity " +
 			"where id=:id";

 

- MyBatis - SQL 여러줄

<update id="update">
 	update item
 	set item_name=#{itemName},
 		price=#{price},
		 quantity=#{quantity}
 	where id = #{id}
</update>

- MyBatis 설정

 

- build.gradle에 의존 관계를 추가한다.

implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'

- 다른 의존 관계와 다르게 버전 정보가 붙는 이유는 스프링 부트가 버전을 관리해주는 공식 라이브러리가 아니기 때문이다.


- 설정

 

- main, test / application.properties 두 곳 전부 아래 내용을 추가해준다.

#MyBatis
mybatis.type-aliases-package=hello.itemservice.domain
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.hello.itemservice.repository.mybatis=trace

1. mybatis.type-aliases-package

   1.1. 마이바티스에서 타입 정보를 사용할 때는 패키지 이름을 적어주어야 하는데, 여기에 명시하면 패키지 이름을 생략할 수 있다.

   1.2. 지정한 패키지와 그 하위 패키지가 자동으로 인식된다.

   1.3. 여러 위치를 지정하려면 , , ; 로 구분하면 된다.

2. mybatis.configuration.map-underscore-to-camel-case

   2.1. JdbcTemplate의 BeanPropertyRowMapper 에서 처럼 언더바를 카멜로 자동 변경해주는 기능을 활성화 한다. 바로 다음에 설명하는 관례의 불일치 내용을 참고하자.

3. logging.level.hello.itemservice.repository.mybatis=trace

   3.1. MyBatis에서 실행되는 쿼리 로그를 확인할 수 있다.


- MyBatis 적용1 - 기본

 

- ItemMapper

@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);
}

1. MyBatis 매핑 MXL을 호출해주는 매퍼 인터페이스다.

2. 해당 인터페이스에 @Mapper 애노테이션을 붙여주어야 한다. 그래야 MyBatis에서 인식할 수 있다.

3. 해당 인터페이스의 메서드를 호출하면 XML의 해당 SQL을 실행하고 결과를 돌려준다.

 

- 같은 위치에 실행할 SQL이 있는 XML  매핑 파일을 만들어주면 된다.

 

- src/main/resources/hello/itemservice/repository/mybatis/ItemMapper.xml

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

</mapper>

- insert - save

 void save(Item item);
<insert id="save" useGeneratedKeys="true" keyProperty="id">
        insert into item (item_name, price, quantity)
        values (#{itemName}, #{price}, #{quantity})
    </insert>

1. id에는 매퍼 인터페이스에 설정한 메서드 이름을 지정하면 된다. 여기서는 매서드 이름이 save()이므로 save로 지정하면 된다.

2. 파라미터는 #{} 문법을 사용한다. 그리고 매퍼에서 넘긴 객체의 프로퍼티 이름을 적어주면 된다.

3. #{} 문법을 사용하면 PreparedStatement를 사용한다. JDBC의 ? 와 비슷하다.

4. useGeneratedKeys 는 데이터베이스가 키를 생성해 주는 IDENTITY 전략일 때 사용한다. keyProperty 는 생성되는 키의 속성 이름을 지정한다. Insert가 끝나면 item 객체의 id 속성에 생성된 값이 입력된다.


- update - update

    void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto updateParam);
<update id="update">
        update item
        set item_name=#{updateParam.itemName},
            price=#{updateParam.price},
            quantity=#{updateParam.quantity}
        where id = #{id}
    </update>

1. 파라미터가 Long id, ItemUpdateDto updateParam으로 2개다. 파라미터가 1개만 있으면 @Param을 지정하지 않아도 되지만, 파라미터가 2개 이상이면 @Param으로 이름을 지정해서 파라미터를 구분해야 한다.


- select - findById

  Optional<Item> findById(Long id);
<select id="findById" resultType="Item">
        select id, item_name, price, quantity
        from item
        where id = #{id}
    </select>

1. resultType 은 반환 타입을 명시하면 된다. 여기서는 결과를 Item 객체에 매핑한다.

   1.1. 앞서 application.properties 에 mybatis.type-aliasespackage=hello.itemservice.domain 속성을 지정한 덕분에 모든 패키지 명을 다 적지는 않아도 된다. 그렇지 않으면 모든 패키지 명을 다 적어야 한다.

   1.2. JdbcTemplate의 BeanPropertyRowMapper 처럼 SELECT SQL의 결과를 편리하게 객체로 바로 변환해준다.

   1.3. mybatis.configuration.map-underscore-to-camel-case=true 속성을 지정한 덕분에 언더스코어를 카멜 표기법으로 자동으로 처리해준다. ( item_name -> itemName )

2. 자바 코드에서 반환 객체가 하나이면 Item , Optional 과 같이 사용하면 되고, 반환 객체가 하나 이상이면 컬렉션을 사용하면 된다. 주로 List 를 사용한다. 다음을 참고하자


- select - findAll

List<Item> findAll(ItemSearchCond itemSearch);
<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>

1. Mybatis는 , 같은 동적 쿼리 문법을 통해 편리한 동적 쿼리를 지원한다.

2. <if>는 해당 조건이 만족하면 구문을 추가한다. 은 적절하게 <where> 문장을 만들어준다.

   2.1. 예제에서 가 모두 실패하게 되면 SQL where 를 만들지 않는다.

   2.2. 예제에서 가 하나라도 성공하면 처음 나타나는 and 를 where 로 변환해준다.


- XML 특수문자

< : &lt;
> : &gt;
& : &amp;

- MyBatis 적용2 - 설정과 실행

 

- MyBatisItemRepository

@Slf4j
@Repository
@RequiredArgsConstructor
public class MyBatisItemRepository implements ItemRepository {

    private final ItemMapper itemMapper;
    //스프링 모듈에서 알아서 인식을 한다. 구현체를 알아서 만들어서 빈에 등록해서 사용한다.

    @Override
    public Item save(Item item) {
        log.info("itemMapper class={}", itemMapper.getClass());
        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);
    }
}

1. ItemRepository 를 구현해서 MyBatisItemRepository 를 만들자.

2. MyBatisItemRepository 는 단순히 ItemMapper 에 기능을 위임한다.

 

- MyBatisConfig

@Configuration
@RequiredArgsConstructor
public class MyBatisConfig {

    private final ItemMapper itemMapper;
    // MyBatis 모듈이 Datasource나 TXManager 같은 것들을 알아서 다 연결시켜준다.

    @Bean
    public ItemService itemService() {
        return new ItemServiceV1(itemRepository());
    }
    @Bean
    public ItemRepository itemRepository() {
        return new MyBatisItemRepository(itemMapper);
    }
}

1. MyBatisConfig 는 ItemMapper 를 주입받고, 필요한 의존관계를 만든다.

 

- ItemServiceApplication

@Import(MyBatisConfig.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(ItemServiceApplication.class, args);
	}

	@Bean
	@Profile("local")
	public TestDataInit testDataInit(ItemRepository itemRepository) {
		return new TestDataInit(itemRepository);
	}
}

- @Import(MyBatisConfig.class) : 앞서 설정한 MyBatisConfig.class 를 사용하도록 설정했다.


- MyBatis 적용3 - 분석

 

- MyBatis 스프링 연동 모듈에서 자동으로 처리해주는데 다음과 같다.

1. 애플리케이션 로딩 시점에 MyBatis 스프링 연동 모듈은 @Mapper 가 붙어있는 인터페이스를 조사한다.

2. 해당 인터페이스가 발견되면 동적 프록시 기술을 사용해서 ItemMapper 인터페이스의 구현체를 만든다.

3. 생성된 구현체를 스프링 빈으로 등록한다

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 : 인프런 - 우아한 형제들 기술이사 김영한의 스프링 완전 정복 (스프링 DB 2편 - 데이터 접근 활용 기술)

728x90