Post

(Spring) DB 관련 - Mybatis

Mybatis가 쿼리를 처리하는 방식?

스프링에서는 DB 커넥션을 어떻게 처리하는가?

  • 쿼리 하나 날릴 때 마다 Connection을 맺고 끊는건 비효율적이므로, Connection Pool을 구성하여 Connection을 생성해두고, DB를 사용해야 할 때 Pool에서 커넥션을 빌려 쓰고 반납하게 되어 있다.
  • Spring에서 제공하는 Connection Pool 인터페이스가 바로 DataSource
    • 각 driver vendor들은 자사 규격에 맞게 DataSource 인터페이스를 구현해서 제공하고 있음!(커넥션 풀 구현체)
      • ojdbc의 OracleDataSource
      • jdbc의 ~~DataSource
    • 각 벤더가 만들어 제공하는게 맞는게, Connection object를 만들어서 가지고 있어야 하는데 이를 만들기 위한 규격을 제공하는게 driver vendor들이 하는 일이고, 그 규격에 맞게 커넥터를 만드는건 자기들이 제일 잘할거니까…
  • 참고하면 도움이 되는 커넥션 풀과 구현체에 대한 설명 : Commons DBCP 이해하기

Mybatis에서 xml 기반으로 쿼리 작성 시 어떻게 resolve되는가?

  • DataSource에서 커넥션을 가져와서 쿼리를 수행하거나, prepared statement를 세팅한다거나, 세션을 유지한다거나 하는 동작을 대행해주는 것이 Mybatis
  • Spring-Mybatis에서는 SqlSessionFactoryBean을 사용하는데, 이 Bean에 DataSource, ConfigLocation, MapperLocation 등을 설정하게 된다.
    • mybatis-spring-boot-starter를 사용하면 config.xml설정은 application.yml에서 할 수 있고, mapper xml은 지정 위치에서 자동으로 파싱한다.
    • 그래서 아래와 같은 설정을 생략할 수 있어 여러모로 설정이 간결해짐
1
2
3
factoryBean.setDataSource(dataSource);
factoryBean.setConfigLocation(applicationContext.getResource("classpath:mybatis/config.xml"));
factoryBean.setMapperLocations(applicationContext.getResources("classpath\*:mybatis/mappers/\*\*/\*.xml"));
  • MapperLocation에 지정된 위치에 있는 xml을 파싱하게 되고, 그 안의 mapper namespace를 따라가 그 인터페이스를 확장한 Bean을 생성해준다!
1
2
3
<mapper namespace="com.example.ex.mapper.TestMapper"> 이면,
interface TestMapper를 확장해서 Bean으로 만들어 준다.
이 때 @Mapper 애너테이션은 그냥 장식이다. xml 보고 resolve하는거라서 안붙여도 상관 없다.

Mybatis에서 애너테이션 기반으로 쿼리 작성 시 어떻게 resolve 되는가?

  • @Mapper 애너테이션을 달면 @MapperScan을 통해 스프링 빈으로 만들어준다.
  • 이 때 쿼리 구문을 넘기기 위해서 @Select()와 같은 애너테이션을 사용함
  • 자바에서는 줄바꿈 스트링이 지원되지 않기 때문에 "" + "" +로 연결해야 해서 아주 지저분하다
  • 그러나 코틀린은 """ 문자열이 지원되기 때문에, 애너테이션 방식이 오히려 깔끔한 것 같다.
    • 가독성도 괜찮고 Mapper interface의 메서드와 실제 query가 xml로 분리되어서 따라가기 귀찮은 점과 관리포인트가 늘어난다는 점도 해소되고..

Mapper 메서드 파라미터가 xml query 인자로 매핑되는 것은?

  • 이름 기반으로 매칭해주고, 원래는 @Param을 써야하지만
  • mybatis.org/mybatis-3/sqlmap-xml.html#constructor
    • 이는 mybatis -> constructor로 매핑해줄 때라 이런 상황과는 반대되는 내용이기는 하지만 아마 같은 사유인 듯?
    • useActualParamName 옵션이 활성화 되면 @Param을 안써줘도 알아서 변수명으로 매핑해서 동작한다고 하는데
    • 이 옵션은 default=true이나, java compile option에 -parameters를 넣어줄 때만 활성화된다. 따라서 이 컴파일 옵션을 넣어주어야 한다.
  • #{param1} 처럼 순서 기반으로 접근해도 되는데 당연 안좋은 방법.

Mybatis는 어떻게 DB에서 꺼내온 데이터를 객체에 매핑해주는가?

MyBatis는 serialize/deserialize에 jackson 사용하지 않는다. 애초에 json io가 아니다.

  • 필드 이름과 컬럼 이름을 기반으로 직접 할당한다.

    • constructor, setter, getter 모두 없는 private 필드여도 이름 매칭으로 잘 할당된다. reflection 기반 할당인 듯.
      • 이름 기반 매칭이므로 필드 순서는 상관 없고, 객체 필드 개수와 DB select 컬럼 개수가 달라도 상관 없다.
    • setter가 있으면 setter를 호출해서 할당해준다.

      • 단, 값이 null인 필드는 애초에 할당 자체를 안하기 때문에 setter가 호출되지 않는다!
        • 그래서 null 필드에 기본값 넣어주는 용도로는 사용 불가
      • 그리고 resultType(resultMap)에 없는 이름의 필드의 setter도 당연히 호출되지 않는다.
        • 그래서 java setDomain() {domain = card.getDomain()} 이렇게는 사용 불가. setDomain이 호출되지 않으니까.
        • 단, 반대로 이렇게는 가능! 사실 이 방향 초기화가 더 맞다!
1
2
3
4
public void setCard(card) {
    this.card = card;
    this.domain = card.getDomain();
}
  • NoArgsConstructor가 없고 직접 정의한 생성자만 있는 경우 이름 기반 매칭이 아니라 순서 기반 매칭한다.
    • 해당 constructor에 정의된 파라미터 순서<>SELECT에 명시된 순서대로 하나씩 넣어준다.
    • 순서 기반이기 때문에 fragile 하다.
    • constructor 파라미터 개수가 DB에서 select하는 컬럼 개수 보다 많으면 IndexOutOfBoundException 발생함.
  • 특히 @Builder 애너테이션은, NoArgs 생성자 안만들고 RequiredArgs 생성자만 만들기 때문에 순서 기반 매칭에 해당한다.
    • 즉, @Builder 객체 1번째 필드와 DB Select 문의 1번째 컬럼을 매핑하기 때문에 순서가 다르면 에러가 발생
    • setter 있으면 이거 호출해서 넣어주는건 동일.
    • @Builder를 사용하면서 이름 기반 매칭을 사용하려면?
      • @NoArgsConstructor가 있어야 이름 기반 매칭 해주는데, 빌더에서 @NoArgs를 붙이려면, @AllArgs가 필요하므로 둘 다 붙여주면 된다.
      • 하지만 빌더와 NoArgs, AllArgs를 같이 사용하는건 좋은 방법은 아니고… useActualParamName 옵션 활성화 해주는 것이 더 나은 해결책.

Mybatis는 어떻게 객체를 query 파라미터에 매핑하는가?

  • field, getter 본다. getter 없는 field도 query에서 사용 가능하고, field 없는 getter도 사용 가능하다.

Mybatis 설정 / 시작하기

mybatis-spring-boot-starter

mybatis get sequence after insert

mybatis에서 enum을 query 파라미터로 쓸 때, lang3을 사용하면 예외가 발생한다

1
2
3
4
<if test="@org.apache.commons.**lang3** .StringUtils@isNotEmpty(userCode)">

--- error 발생
Caused by: java.lang.IllegalArgumentException: Unable to convert type com.naver.dbill.common.enums.UserCode of AB to type of java.lang.CharSequence
  • 애초에 enum을 StringUtils로 비교한다는 것 자체가 type이 안맞기 때문임.
  • lang3이 아닌 lang.StringUtils를 쓰면 예외 없이 동작은 하는데, 타입 체크를 안하기 때문. lang은 안쓰는 것이 좋다.
  • enum은 java userCode != null로 비교해주면 된다.

xml based query VS annotation based query

코틀린은 """로 multiline 지원이 되니까 후자가 더 나은데… java는 엔터를 \n +로 연결해야 해서 그냥 xml 쓰는게 낫다…

where 절에 기본 조건이 없어 AND가 하나 남아 문법 오류 발생하는 경우

1
2
3
4
5
6
FROM och
<where>
    <if test="@org.apache.commons.lang3.StringUtils@isNotEmpty(userId)">
    AND och.userId = #{userId}
    </if>
</where>

이런 식으로 WHERE 키워드가 아니라 xml <where>을 사용하면 된다.

MyBatis Batch Insert

github.com/mybatis/mybatis-3/wiki/FAQ#how-do-i-code-a-batch-insert

This post is licensed under CC BY 4.0 by the author.