[Spring] DB 관련 : Mybatis CustomTypeHandler
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 설정이라면
<setting name="jdbcTypeForNull" value="NULL"/>
```
CustomTypeHandler 정의
```java
/* myBatis config typeHandler 로 등록되어 Boolean 을 Y, N, null 로 변환해준다. */
@MappedTypes(Boolean.class)
@MappedJdbcTypes(JdbcType.CHAR)
public class BaseBooleanTypeHandler extends BaseTypeHandler<Boolean> {
...
#{param, jdcbType=VARCHAR}
```
- https://mybatis.org/mybatis-3/ko/configuration.html#typeHandlers
- 애너테이션 사용 시 주의. 꼼꼼하게 읽어보고 사용해야 함.
- 마이바티스는 DB에서 꺼내온 데이터를 어떤 타입핸들러에서 처리할지를, [javaType, jdbcType] 두 개를 이용해 결정한다.
- jdcbType을 쿼리에 명시하지 않은 경우, ``java javaType=[TheJavaType], jdbcType=null``조합을 사용한다.
- 즉 기본으로 제공되는 AClassTypeHandler<A>와 내가 직접 정의한 AClassTypeHandler<A>가 모두 존재한다면, 후자를 사용하기 위해서는
- @MappedJdbcTypes(TheJdbcType)을 사용하고, 쿼리 파라미터에 해당 jdbcType을 명시하거나,
- ``java javaType=[TheJavaType], jdbcType=[TheJdbcType]`` 에 대해서 내 핸들러를 탄다.
- 쿼리 파라미터에 아예 typeHandler를 적어서 내 핸들러를 타도록 명시하거나,
- => 이 경우 typeAliases로 Handler가 등록 되어 있어야 prefix없이 쓴다.
- @MappedJdbcTypes 애너테이션을 아예 사용하지 않고 @MappedTypes만 쓰거나,
- 예를 들면, 모든 LocalDateTime 타입은 이 TypeHandler에서만 처리할거라면. 많이 쓰는 방법.
- => 애너테이션을 안쓰면 AllJdbcType에 대한 핸들러가 정의된다.
- 따라서 ``java javaType=[TheJavaType], jdbcType=[null&AllType]`` 이 내 핸들러를 탄다
- @MappedJdcbTypes(value=TheJdcbType, includeNullJdbcType = true) 로 적어주어야 한다.
- ``java javaType=[TheJavaType], jdbcType=[null&TheJdbcType]`` 에 대해서 내 핸들러를 탄다
- 이건 조금 애매한 방법일 것 같은게... 애초에 핸들러를 2개 이상 유지해야 한다는 것은, jdbcType에 따라 다른 핸들러를 태워서 처리를 다르게 할 필요가 있기 때문이다.
- db 상에 VARCHAR인 것도 jdbcType 명시 안하면 null로 넘어올거고, DATE인 것도 jdbcType 명시 안하면 null로 넘어오게 될텐데, 이러면 두 jdbcType 모두 includeNullJdbcType = true인 핸들러를 타게 된다.
- 각기 다른 핸들러를 타기 위해서는 둘 중 하나의 db타입 파라미터에는 jdbcType을 꼭 명시해줘야 되는데... 그럴 바에야 두 타입 모두 jdbcType을 명시해주는게 더 나으니까.
- @MappedJdbcTypes(TheJdbcType)을 사용하고, 쿼리 파라미터에 해당 jdbcType을 명시하거나,
CustomTypeHandler for Enum
Enum <> DB 입출력 시 기본적으로 Enum.name()을 이용해 변환하도록 되어 있는데,
Enum 필드의 DB 입출력을 Enum.name()이 아니라 별도 필드 값으로 입출력 하고 싶은 경우 어떻게 해야 하는가?
방법 1
- www.holaxprogramming.com/2015/11/12/spring-boot-mybatis-typehandler/
- Enum 1개 마다 TypeHandler 1개를 정의해주어야 하는 방식이라, 사용하기 썩 좋은 방식은 아니다.
방법 2 : interface (추천)
- Mybatis 3.4.3 부터 @MappedType(interface) 를 붙여주면, 해당 interface를 상속한 Enum은 그 TypeHandler를 이용하게 된다.
- 코드는 gist로 대체
방법 3 : annotation (추천)
- 근데 interface가 별로 마음에 들지 않는다. annotation을 사용할 수 있는 방법은 없나?
- " Mybatis는 ~mapper.xml에 등장하는 모든 Enum을 스캔해서 각각을 파라미터로 기본으로 제공되는 EnumTypeHandler<E>를 생성해준다. " 는 점을 이용하면 별도로 TypeHandler를 정의 할 필요가 없을 것 같은데...
- Mybatis 3.4.5 부터 기본으로 제공되는 EnumTypeHandler 대신, Custom 타입핸들러를 기본 EnumTypeHandler로 만들 수 있다.
```java
// MyBatis 코드에 다음과 같은 부분이 있음. (>= 3.4.5)
Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
configuration.setDefaultEnumTypeHandler(typeHandler);
// in config.xml
<setting name="defaultEnumTypeHandler" value="~~~.~~~.EnumDbValueTypeHandler"/>
```
- annotation을 사용한 방식은 defaultEnumTypeHandler를 지정해주어야 한다는 단점, 그러나 interface 대비 좀 더 깔끔하다. 취향대로 쓰면 될 듯.
- 코드는 gist로 대체
'Java Stack > Spring' 카테고리의 다른 글
[Spring] EventListener (0) | 2022.04.17 |
---|---|
테스트 클래스를 일정 수 이상 묶어서 실행하면, 어느 정도 실행하다가 갑자기 JDBC Connection을 무한히 대기하는 현상 (0) | 2022.01.12 |
[Spring] WebClient (0) | 2021.03.14 |
spring-webmvc 5.2.4 이하(springboot 2.2.5 이하) 버전에서 발생하는 응답지연 현상 (0) | 2021.01.14 |
Java Servlet 이란 (0) | 2020.06.15 |