(Spring) DB 관련 - Mybatis
Mybatis가 쿼리를 처리하는 방식?
스프링에서는 DB 커넥션을 어떻게 처리하는가?
- 쿼리 하나 날릴 때 마다 Connection을 맺고 끊는건 비효율적이므로, Connection Pool을 구성하여 Connection을 생성해두고, DB를 사용해야 할 때 Pool에서 커넥션을 빌려 쓰고 반납하게 되어 있다.
- Spring에서 제공하는 Connection Pool 인터페이스가 바로
DataSource
- 각 driver vendor들은 자사 규격에 맞게
DataSource
인터페이스를 구현해서 제공하고 있음!(커넥션 풀 구현체)- ojdbc의
OracleDataSource
- jdbc의
~~DataSource
- ojdbc의
- 각 벤더가 만들어 제공하는게 맞는게, Connection object를 만들어서 가지고 있어야 하는데 이를 만들기 위한 규격을 제공하는게 driver vendor들이 하는 일이고, 그 규격에 맞게 커넥터를 만드는건 자기들이 제일 잘할거니까…
- 각 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이 호출되지 않으니까. - 단, 반대로 이렇게는 가능! 사실 이 방향 초기화가 더 맞다!
- 그래서
- 단, 값이 null인 필드는 애초에 할당 자체를 안하기 때문에 setter가 호출되지 않는다!
- constructor, setter, getter 모두 없는 private 필드여도 이름 매칭으로 잘 할당된다. reflection 기반 할당인 듯.
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
- http://www.mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
- SpringBoot에서 Mybatis와 H2 설정에 대해 아주 잘 정리되어 있는 블로그
- SpringBoot를 사용하면 대부분의 설정을 application.properties에서 처리할 수 있다. 자동으로 해주는 부분도 많고
- 세부적인 설정이 필요한 경우만 resources/mybatis-config.xml을 작성.
mybatis get sequence after insert
- mybatis는 auto_increment 설정된 key 컬럼의 테이블에 INSERT 하면서 동시에 증가된 key 값을 가져올 수 있다.
- https://roqkffhwk.tistory.com/180
- https://taetaetae.github.io/2017/04/04/mybatis-useGeneratedKeys/
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.