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 정의
```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
`` E[] enumConstants``를 유지할지,
`` Map<String, E> enumConstantDirectory``를 유지할지는 상황에 따라서. Enum은 둘 다 가지고 있긴 함.
방법 1
- www.holaxprogramming.com/2015/11/12/spring-boot-mybatis-typehandler/
- Enum 1개 마다 TypeHandler 1개를 정의해주어야 하는 방식이라, 사용하기 썩 좋은 방식은 아니다.
```java
@Getter
@RequiredArgsConstructor
public enum ExampleEnum implements EnumUsingDbCode {
EX1,
EX2
;
@Override
public String getDbCode() {
return this.name().toLowerCase();
}}
```
```java
/**
얘를 ExampleEnum 안에 안두고 따로 둔 것은
1. setTypeHandlersPackage 관리 때문에 특정 패키지에 몰아두는게 나을 수 있음.
2. ExampleEnum은 POJO로 두는게 깔끔함.
*/
@MappedTypes(ExampleEnum.class)
public class ExampleEnumTypeHandler extends EnumUsingDbCodeTypeHandler<ExampleEnum> {
public ExampleEnumTypeHandler() {
super(ExampleEnum.class);
}
}
```
```java
public abstract class EnumUsingDbCodeTypeHandler<E extends Enum<E>> extends BaseTypeHandler<EnumUsingDbCode> {
private final Class<E> type;
private final EnumUsingDbCode[] enums;
protected EnumUsingDbCodeTypeHandler(Class<E> type) {
this.type = type;
this.enums = (EnumUsingDbCode[]) type.getEnumConstants();
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, EnumUsingDbCode enumUsingDbCode, JdbcType jdbcType) throws SQLException {
ps.setString(i, enumUsingDbCode.getDbCode());
}
@Override
public EnumUsingDbCode getNullableResult(ResultSet rs, String columnName) throws SQLException {
String dbCode = rs.getString(columnName);
return getEnumDbCode(dbCode);
}
@Override
public EnumUsingDbCode getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String dbCode = rs.getString(columnIndex);
return getEnumDbCode(dbCode);
}
@Override
public EnumUsingDbCode getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String dbCode = cs.getString(columnIndex);
return getEnumDbCode(dbCode);
}
private EnumUsingDbCode getEnumDbCode(String dbCode) throws SQLException {
return Stream.of(this.enums)
.filter(enumUsingDbCode -> enumUsingDbCode.getDbCode().equals(dbCode))
.findFirst()
.orElseThrow(SQLException::new); }
}
```
방법 2
github.com/mybatis/mybatis-3/issues/42
```java
@MappedType(...)
public class EnumUsingDbCodeTypeHandler<E extends Enum<E> & EnumUsingDbCode> extends BaseTypeHandler<E> {
```
- 이렇게 하면, 타입 핸들러는 매번 정의 안해줘도 되지만 MappedType에 이 핸들러를 탈 대상 Enum을 추가해주어야 한다는 것은 똑같다.
- 자동으로 해당 타입과 하위타입 클래스들을 스캔해서 TypeHandler를 생성해주는게 아니기 때문에...
방법 3 : 추천
- 근데 방법1,2 가 별로 마음에 들지 않는다. 이 방법 말고 다른 방법은 없나?
- " Mybatis는 ~mapper.xml에 등장하는 모든 Enum을 스캔해서 각각을 파라미터로 기본으로 제공되는 EnumTypeHandler<E>를 생성해준다. " 는 점을 이용하면 별도로 TypeHandler를 정의 할 필요가 없을 것 같은데...
- github.com/mybatis/mybatis-3/issues/970
- mybatis.org/mybatis-3/ko/configuration.html
아래 설정을 이용해서 기본으로 제공되는 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="~~~.~~~.DefaultEnumTypeHandler"/>
```
코드가 길어서 gist로 대체
- gist.github.com/umbum/5410363d4be0637a127d9b3a81955ee0
- interface 사용한 방식
- 이만 해도 괜찮긴 한데... @annotation을 사용할 수는 없을까?
- gist.github.com/umbum/492c839c2ad5a9d09f638369b481f9c8
- annotation 사용한 방식 (추천)
'Languages & Frameworks > Spring' 카테고리의 다른 글
[Spring] DB 관련 : Mybatis CustomTypeHandler (0) | 2021.03.31 |
---|---|
[Spring] WebClient (0) | 2021.03.14 |
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 |