mybatis 공부 : 기본

Chooooo·2023년 11월 3일
0

인턴생활

목록 보기
7/19

이번 프로젝트에서 수정사항을 지시 받았는데, 해당 코드를 mybatis로 작성해야 한다고 들었다. 그런데 나는 Spring Data Jpa, Querydsl로 데이터 접근을 해왔던지라, mybatis에 대해서는 잘 알지 못한다.

  • 인턴이니 시키면 한다!
  • 이번 기회에 필수적인 기능들을 공부하고 공식 문서 번역이 잘 되어 있기 때문에 보면서 하고자 한다.

데이터베이스 연동

데이터 접근 기술은 실제 데이터베이스에 접근해서 데이터를 잘 저장하고 조회할 수 있는지 확인하는 것이 필요하다.

데이터베이스 분리

로컬에서 사용하는 애플리케이션 서버와 테스트에서 같은 데이터베이스를 사용한다면 테스트에서 문제가 발생할 수 있다.

  • 이런 문제를 해결하려면 테스트를 다른 환경과 철저하게 분리해야 한다.

가장 간단한 방법은 테스트 전용 데이터베이스를 별도로 운영하는 것이다.


😎 MyBatis

MyBatis는 SQL을 XML에 편리하게 작성할 수 있고 또 동적 쿼리를 매우 편리하게 작성할 수 있다.

<update id="update">
 update item
 set item_name=#{itemName},
 price=#{price},
 quantity=#{quantity}
 where id = #{id}
</update>
  • MyBatis는 XML에 작성하기 때문에 라인이 길어져도 문자 더하기에 대한 불편함이 없다.

MyBatis - 동적 쿼리

<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>

👻 지금부터 MyBatis에서 주로 사용하는 기능 위주로 다룰 것이다.

  • 필수적인 내용들로 개발을 할 수 있을 정도로..
  • 추가로 필요한 내용은 공식 사이트에서 찾아서 사용하자
  • 공식 사이트가 한글로 잘 번역되어 있어서 원하는 기능을 편리하게 찾아볼 수 있다.

[공식 사이트]

😊 MyBatis 추가

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

application.properties

먼저 application.properties


# server port
server.port=8080

#MySQL Connection
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/itDiaLocal?serverTimeZone=UTC
spring.datasource.username=root
spring.datasource.password=1234

# Logging
logging.level.root=info
logging.level.heelo.itemservice.repository.mybatis=trace

#MyBatis Setting
# 해당 패키지 관련된 부분은 mybatis에서 자동으로 인식해서 파라미터나 응답값에 패키지명을 지정하지 않아도 됨
mybatis.type-aliases-package=com.example.demo
# 데이터베이스에는 underscore가 되어 있어도 객체의 camel-case를 자동으로 해줌./
mybatis.configuration.map-underscore-to-camel-case=true 

mybatis.mapper-locations=com/example/demo/repository/mybatis/**/*.xml
#mybatis.configuration.call-setters-on-nulls=true

#참고 - XML 파일 경로 수정하기
#> XML 파일을 원하는 위치에 두고 싶으면 application.properties 에 다음과 같이 설정하면 된다.
#> mybatis.mapper-locations=classpath:mapper/**/*.xml
#>
#> 이렇게 하면 resources/mapper 를 포함한 그 하위 폴더에 있는 XML을 XML 매핑 파일로 인식한다. 이 경우 파일 이름은 자유롭게 설정해도 된다.
#>
#> 참고로 테스트의 application.properties 파일도 함께 수정해야 테스트를 실행할 때 인식할 수 있다.

😓 분석
mybatis.type-aliases-package

  • 마이바티스에서 타입 정보를 사용할 때는 패키지 이름을 적어주어야 하는데, 여기에 명시하면 패키지 이름을 생략할 수 있다.
  • 지정한 패키지와 그 하위 패키지가 자동으로 인식된다.
  • 여러 위치를 지정하려면 , , ; 로 구분하면 된다.

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

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

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

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

mybatis.mapper-locations 속성은 MyBatis가 SQL 맵핑 XML 파일을 찾는 위치를 지정한다.

  • classpath : 접두사는 클래스패스에서 해당 위치를 찾으라는 의미이다.
    따라서 classpath:mapper/**/*.xml 은 클래스패스의 mapper 디렉토리 및 그 하위 디렉토리를 기본으로 한다.

반면에, mybatis/mapper/**/*.xml 는 mybatis 디렉토리가 클래스패스의 루트 디렉토리에 있어야 한다는 의미이다. 하지만, 실제로는 mapper 디렉토리가 src/main/resources 아래에 있으므로, 이 설정은 올바르지 않다. 따라서 classpath:mapper/**/*.xml로 변경해야 한다.

이렇게 함으로써 MyBatis는 src/main/resources/mapper 디렉토리 및 그 하위 디렉토리에서 SQL 맵핑 XML 파일을 찾을 수 있게 된다.

🎈 관례의 불일치

자바 객체에는 주로 카멜( camelCase ) 표기법을 사용한다. itemName 처럼 중간에 낙타 봉이 올라와 있는 표기법이다.
반면에 관계형 데이터베이스에서는 주로 언더스코어를 사용하는 snake_case 표기법을 사용한다.
item_name 처럼 중간에 언더스코어를 사용하는 표기법이다.
이렇게 관례로 많이 사용하다 보니 map-underscore-to-camel-case 기능을 활성화 하면 언더스코어 표기법을 카멜로 자동 변환해준다. 따라서 DB에서 select item_name 으로 조회해도 객체의
itemName ( setItemName() ) 속성에 값이 정상 입력된다.
정리하면 해당 옵션을 켜면 snake_case 는 자동으로 해결되니 그냥 두면 되고, 컬럼 이름과 객체 이름 완전히 다른 경우에는 조회 SQL에서 별칭을 사용하면 된다

MyBatis 적용1 - 기본

이제부터 본격적으로 MyBatis를 사용해서 데이터베이스에 데이터를 저장해보자.
XML에 작성한다는 점을 제외하고는 JDBC 반복을 줄여준다는 점에서 기존 JdbcTemplate과 거의 유사하다.

ItemMapper

@Mapper
public interface ItemMapper {

    void save(Item item);

    void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto updateParam);

    List<Item> findAll(@Param("itemSearch")ItemSearchCond itemSearch);  //파라미터가 하나인 경우에는 @Param을 쓰지 않아도 된다.

    Optional<Item> findById(@Param("id" )Long id);
}
  • 마이바티스 매핑 XML을 호출해주는 매퍼 인터페이스이다.
  • 이 인터페이스에는 @Mapper 애노테이션을 붙여주어야 한다. 그래야 MyBatis에서 인식할 수 있다.
  • 이 인터페이스의 메서드를 호출하면 다음에 보이는 xml 의 해당 SQL을 실행하고 결과를 돌려준다.
  • ItemMapper 인터페이스의 구현체에 대한 부분은 뒤에 별도로 설명한다.
  • 이제 같은 위치에 실행할 SQL이 있는 XML 매핑 파일을 만들어주면 된다.
  • 참고로 자바 코드가 아니기 때문에 src/main/resources 하위에 만들되, 패키지 위치는 맞추어 주어야 한다.

src/main/resources/hello/itemservice/repository/mybatis/ItemMapper.xml 처럼!!

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">
<!--위 xml 에 대한 것은 먼저 선언-->
<!--기본 문법은 mapper, 즉 namespace를 지정해줘야 하는데, interface 이름을 지정해줘야해. 패키지명이랑, 인터페이스명을 넣어주기 이러면 연동해줌.,-->
<mapper namespace="com.example.demo.repository.mybatis.ItemMapper">
<!--    insert, update, select 태그들이 있으니 활용하면 됨. -> id에는 Mapper 인터페이스에 설정해뒀던 메서드명을 맞춰줘야 해!-->

    <insert id="save" useGeneratedKeys="true" keyProperty="id"> <!-- 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 != ''">      <!-- itemName이 비어있거나, 공백이 아니라면 -->
                and item_name like concat('%',#{itemName},'%')
            </if>
            <if test="maxPrice != null">
                and price &lt;= #{maxPrice}
            </if>
        </where>
    </select>
</mapper>
  • namespace : 앞서 만든 매퍼 인터페이스를 지정하면 된다.
  • 주의 : 경로와 파일 이름에 주의하자

<mapper> 태그의 namespace 속성은 해당 매퍼 XML 파일이 연결되는 매퍼 인터페이스의 완전한 클래스 이름을 지정해야 한다 !!

따라서, namespace 속성의 값은 해당 XML 파일과 연결된 ItemMapper 인터페이스의 완전한 클래스 이름, 즉 com.example.demo.repository.mybatis.ItemMapper와 일치해야 한다.

  • 따라서 XML 파일의 위치나 mybatis.mapper-locations 설정을 변경했다고 하더라도, namespace 속성의 값은 매퍼 인터페이스의 완전한 클래스 이름과 일치해야 한다!!
    • 이것이 MyBatis가 XML 파일과 매퍼 인터페이스를 올바르게 연결하는 데 필요한 조건이다.

참고 - XML 파일 경로 수정하기

XML 파일을 원하는 위치에 두고 싶으면 application.properties 에 다음과 같이 설정하면 된다.
mybatis.mapper-locations=classpath:mapper/**/*.xml
이렇게 하면 resources/mapper 를 포함한 그 하위 폴더에 있는 XML을 XML 매핑 파일로 인식한다. 이 경우 파일 이름은 자유롭게 설정해도 된다.
참고로 테스트의 application.properties 파일도 함께 수정해야 테스트를 실행할 때 인식할 수 있다.

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>

Insert SQL은 <insert> 를 사용하면 된다.
id 에는 매퍼 인터페이스에 설정한 메서드 이름을 지정하면 된다. 여기서는 메서드 이름이 save() 이므로 save로 지정하면 된다.
파라미터는 #{} 문법을 사용하면 된다. 그리고 매퍼에서 넘긴 객체의 프로퍼티 이름을 적어주면 된다.
#{} 문법을 사용하면 PreparedStatement 를 사용한다. JDBC의 ? 를 치환한다 생각하면 된다.
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>
  • Update SQL은 <update> 를 사용하면 된다.
    여기서는 파라미터가 Long id , ItemUpdateDto updateParam 으로 2개이다. 파라미터가 1개만 있으면 @Param 을 지정하지 않아도 되지만, 파라미터가 2개 이상이면 @Param 으로 이름을 지정해서 파라미터를 구분해야 한다.
  • 맘 편하게 @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>
  • Select SQL은 <select> 를 사용하면 된다.

  • resultType 은 반환 타입을 명시하면 된다. 여기서는 결과를 Item 객체에 매핑한다.
    앞서 application.propertiesmybatis.type-aliases-package=hello.itemservice.domain 속성을 지정한 덕분에 모든 패키지 명을 다 적지는 않아도 된다. 그렇지 않으면 모든 패키지 명을 다 적어야 한다.

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

  • mybatis.configuration.map-underscore-to-camel-case=true 속성을 지정한 덕분에
    언더스코어를 카멜 표기법으로 자동으로 처리해준다. ( item_name itemName )
    자바 코드에서 반환 객체가 하나이면 Item , Optional<Item> 과 같이 사용하면 되고, 반환 객체가 하나 이상이면 컬렉션을 사용하면 된다. 주로 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>
  • Mybatis는 <where> , <if> 같은 동적 쿼리 문법을 통해 편리한 동적 쿼리를 지원한다.
  • <if> 는 해당 조건이 만족하면 구문을 추가한다.
  • <where> 은 적절하게 where 문장을 만들어준다.
    예제에서 <if> 가 모두 실패하게 되면 SQL where 를 만들지 않는다.
    예제에서 <if> 가 하나라도 성공하면 처음 나타나는 and 를 where 로 변환해준다.

XML 특수문자

그런데 가격을 비교하는 조건을 보자
and price &lt;= #{maxPrice}
여기에 보면 <= 를 사용하지 않고 &lt;= 를 사용한 것을 확인할 수 있다. 그 이유는 XML에서는 데이터 영역에 < , > 같은 특수 문자를 사용할 수 없기 때문이다. 이유는 간단한데, XML에서 TAG가 시작하거나 종료할 때 < , > 와 같은 특수문자를 사용하기 때문이다.

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

XML CDATA 사용

다른 해결 방안으로는 XML에서 지원하는 CDATA 구문 문법을 사용하는 것이다. 이 구문 안에서는 특수문자를 사용할 수 있다. 대신 이 구문 안에서는 XML TAG가 단순 문자로 인식되기 때문에 <if> , <where> 등이 적용되지 않는다.

<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">
 <![CDATA[
 and price <= #{maxPrice}
 ]]>
 </if>
 </where>
</select>
profile
back-end, 지속 성장 가능한 개발자를 향하여

0개의 댓글