Mybatis가 쿼리를 처리하는 방식?
스프링에서는 DB 커넥션을 어떻게 처리하는가?
- 쿼리 하나 날릴 때 마다 Connection을 맺고 끊는건 비효율적이므로, Connection Pool을 구성하여 Connection을 생성해두고, DB를 사용해야 할 때 Pool에서 커넥션을 빌려 쓰고 반납하게 되어 있다.
- Spring에서 제공하는 Connection Pool 인터페이스가 바로 `` DataSource``
- 각 driver vendor들은 자사 규격에 맞게 `` DataSource`` 인터페이스를 구현해서 제공하고 있음!(커넥션 풀 구현체)
- ojdbc의 `` OracleDataSource``
- jdbc의 `` ~~DataSource``
- 각 벤더가 만들어 제공하는게 맞는게, Connection object를 만들어서 가지고 있어야 하는데 이를 만들기 위한 규격을 제공하는게 driver vendor들이 하는 일이고, 그 규격에 맞게 커넥터를 만드는건 자기들이 제일 잘할거니까...
- 각 driver vendor들은 자사 규격에 맞게 `` DataSource`` 인터페이스를 구현해서 제공하고 있음!(커넥션 풀 구현체)
- 참고하면 도움이 되는 커넥션 풀과 구현체에 대한 설명 : 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은 지정 위치에서 자동으로 파싱한다.
- 그래서 아래와 같은 설정을 생략할 수 있어 여러모로 설정이 간결해짐
```java
factoryBean.setDataSource(dataSource);
factoryBean.setConfigLocation(applicationContext.getResource("classpath:mybatis/config.xml"));
factoryBean.setMapperLocations(applicationContext.getResources("classpath*:mybatis/mappers/**/*.xml"));
```
- MapperLocation에 지정된 위치에 있는 xml을 파싱하게 되고, 그 안의 mapper namespace를 따라가 그 인터페이스를 확장한 Bean을 생성해준다!
```xml
<mapper namespace="com.example.ex.mapper.TestMapper"> 이면,
interface TestMapper를 확장해서 Bean으로 만들어 준다.
이 때 @Mapper 애너테이션은 그냥 장식임! xml 보고 resolve하는거라서 안붙여도 상관 없다.
```
Mybatis에서 애너테이션 기반으로 쿼리 작성 시 어떻게 resolve 되는가?
- `` @Mapper`` 애너테이션을 달면 `` @MapperScan``을 통해 스프링 빈으로 만들어준다.
- 이 때 쿼리 구문을 넘기기 위해서 `` @Select()``와 같은 애너테이션을 사용함
- 자바에서는 줄바꿈 스트링이 지원되지 않기 때문에 ``java "" + "" +``로 연결해야 해서 아주 지저분하다
- 그러나 코틀린은 ``kt """`` 문자열이 지원되기 때문에, 애너테이션 방식이 오히려 깔끔한 것 같다.
- 가독성도 괜찮고 Mapper interface의 메서드와 실제 query가 xml로 분리되어서 따라가기 귀찮은 점과 관리포인트가 늘어난다는 점도 해소되고..
Mapper 메서드 파라미터가 xml query 인자로 매핑되는 것은?
- 이름 기반으로 매칭해주고, 원래는 @Param을 써야하지만
- mybatis.org/mybatis-3/sqlmap-xml.html#constructor
- 이는 mybatis -> constructor로 매핑해줄 때라 이런 상황과는 반대되는 내용이기는 하지만 아마 같은 사유인 듯?
- `-parameters`와 `useActualParamName` 부분을 보면 이 옵션이 기본적으로 활성화 되어 있어 @Param을 안써줘도 알아서 변수명으로 매핑해서 동작한다.
- #{param1} 처럼 순서 기반으로 접근해도 되는데 당연 안좋은 방법.
Mybatis는 어떻게 DB에서 꺼내온 데이터를 객체에 매핑해주는가?
- 필드 이름과 컬럼 이름을 기반으로 직접 할당한다.
- 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 기반 할당인 듯.
```java
public void setCard(card) {
this.card = card;
this.domain = card.getDomain();
}
```
- NoArgsConstructor가 없고 직접 정의한 생성자만 있는 경우 주의해야 함!
- 직접 정의한 생성자의 파라미터에 순서대로 넘기는 듯.
- @Builder와 같이 사용할 때 주의해야 함!
- @Builder만 단독으로 사용 시 이름 기반 매칭을 사용하는게 아니라 순서 기반 매칭을 사용함!!!
- 객체의 1번째 필드와 DB Select 문의 1번째 컬럼을 매핑하기 때문에 순서가 다르면 에러가 발생
- 객체의 필드 개수가 DB에서 select하는 컬럼 개수 보다 많으면 IndexOutOfBoundException이 발생함
- setter 있으면 이거 호출해서 넣어주는건 동일.
- @Builder를 사용하면서 이름 기반 매칭을 사용하려면?
- @NoArgsConstructor, @AllArgsConstructor를 추가로 붙여주면 됨! + @Data 까지 4개 붙여주는게 일반적
- @Builder만 단독으로 사용 시 이름 기반 매칭을 사용하는게 아니라 순서 기반 매칭을 사용함!!!
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을 사용하면 예외가 발생한다
```xml
<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``로 비교해주면 된다.
jdbcType : nullable column에 null이 들어갈 때?
- MyBatis는 nullable 컬럼의 parameter로 null이 넘어왔을 때, jdbcType이 명시되어 있지 않으면 TypeException을 던진다.
- setNull로 해당 타입에 맞는 null값(VARCHAR인 경우 "")을 넣어줘야 하는데, 뭘 넣어줄지 모르니까 예외가 발생하는 것
- The JDBC Type is required by JDBC for all nullable columns, if null is passed as a value. You can investigate this yourself by reading the JavaDocs for the PreparedStatement.setNull() method.
[www.mybatis.org/mybatis-3/sqlmap-xml.html]
- 반대로 null이 아닌 값이 넘어왔을 때는 해당 자바 타입의 setNonNullParameter를 호출하는데 여기서 parameter의 타입 -> DB 타입으로 변환해주어야 한다. 적절하게 변환하지 않으면 "부적합한 열 유형" 예외가 발생함.
- 모든 nullable column에 jdbcType을 명시해주기 귀찮다면, 다음 방법으로 처리할 수 있음.
```java
sqlSessionFactory!!.configuration.jdbcTypeForNull = JdbcType.NULL
// xml base 설정도 가능
```
CustomTypeHandler 정의
- https://mybatis.org/mybatis-3/ko/configuration.html#typeHandlers
- 애너테이션 사용 시 주의. 꼼꼼하게 읽어보고 사용해야 함.
- 마이바티스는 타입핸들러를 선택하기 위해 ``java javaType=[TheJavaType], jdbcType=null``조합을 사용한다.
- jdbcType이 쿼리 파라미터에 명시되어 있지 않은 경우 javaType만 본다!
- 즉 기본으로 제공되는 AClassTypeHandler<A>와 내가 직접 정의한 AClassTypeHandler<A>가 모두 존재한다면, 후자를 사용하기 위해서는
- 쿼리 파라미터에 jdbcType을 명시하거나,
- => @MappedJdbcTypes에 적은 타입과 동일한 타입. 해당 쿼리 파라미터는 ``java jdbcType=null``이 아니라 `` [TheJdbcType]``으로 넘어오니 내가 정의한 타입핸들러를 타게 된다.
- 쿼리 파라미터에 typeHandler를 적어 아예 내 핸들러를 타도록 명시하거나,
- => 이 경우 typeAliases로 Handler가 등록 되어 있어야 prefix없이 쓴다.
- MappedJdbcTypes 애너테이션을 아예 사용하지 않거나,
- ``java javaType=[TheJavaType], jdbcType=[null&AllType]`` 에 대해서 내 핸들러를 탄다
- 사용하면서 includeNullJdbcType = true로 적어주어야 한다.
- ``java javaType=[TheJavaType], jdbcType=[null&TheJdbcType]`` 에 대해서 내 핸들러를 탄다
- 이건 조금 애매한 방법일 것 같은게... db 상에 VARCHAR인 것도 jdbcType 명시 안하면 null로 넘어올거고, DATE인 것도 jdbcType 명시 안하면 null로 넘어오게 될텐데, 이러면 두 타입 모두 includeNullJdbcType = true인 핸들러를 타게 된다.
- 각각 다른 핸들러를 타기 위해서는 둘 중 하나의 db타입 파라미터에는 jdbcType을 꼭 명시해줘야 되는데...
- 그럴 바에야 두 타입 모두 jdbcType을 명시해주는게 더 나으니까.
- 쿼리 파라미터에 jdbcType을 명시하거나,
```java
/* myBatis config typeHandler 로 등록되어 Boolean 을 Y, N, null 로 변환해준다. */
@MappedTypes(Boolean.class)
@MappedJdbcTypes(JdbcType.CHAR)
public class BaseBooleanTypeHandler extends BaseTypeHandler<Boolean> {
...
```
xml based query VS annotation based query
코틀린은 `` """``로 multiline 지원이 되니까 후자가 더 나은데... java는 엔터를 \n +로 연결해야 해서 그냥 xml 쓰는게 낫다...
where 절에 기본 조건이 없어 AND가 하나 남아 문법 오류 발생하는 경우
```xml
FROM och
<where>
<if test="@org.apache.commons.lang3.StringUtils@isNotEmpty(userId)">
AND och.userId = #{userId}
</if>
</where>
```
이런 식으로 WHERE 키워드가 아니라 xml `` <where>``을 사용하면 된다.
'Languages & Frameworks > Spring' 카테고리의 다른 글
Java Servlet 이란 (0) | 2020.06.15 |
---|---|
RestTemplate은 어떻게 response Object를 DataType <T>로 변환하는가 (0) | 2020.03.27 |
[Spring] DB 관련 : Mybatis (0) | 2020.03.04 |
[Spring] profile로 beta, real 빌드 구분하기 (0) | 2020.03.03 |
[Spring] Controller에서 사용하는 애너테이션 (0) | 2019.09.25 |
Spring AOP / @annotation resolve (0) | 2019.07.30 |