Last active
November 23, 2021 04:58
-
-
Save umbum/492c839c2ad5a9d09f638369b481f9c8 to your computer and use it in GitHub Desktop.
MyBatis Custom DefaultEnumTypeHandler using annotation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.lang.reflect.AnnotatedElement; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Member; | |
import java.lang.reflect.Method; | |
import java.sql.CallableStatement; | |
import java.sql.PreparedStatement; | |
import java.sql.ResultSet; | |
import java.sql.SQLException; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.stream.Collectors; | |
import org.apache.ibatis.type.BaseTypeHandler; | |
import org.apache.ibatis.type.JdbcType; | |
/** | |
* Enum.name() 대신, {@link DbValue}가 붙어 있는 Field/Method value를 DB 입출력 값으로 사용하는 EnumTypeHandler | |
*<p> | |
* MyBatis ~mapper.xml에서 사용되는 모든 Enum 대한 기본 Handler로 사용하려면, | |
* <code>config.xml</code>에서 기본 EnumTypeHandler로 설정 필요. (v3.4.5 이상 가능) | |
* <pre> | |
* <setting name="defaultEnumTypeHandler" value="~~~.~~~.EnumDbValueTypeHandler"/> | |
* </pre> | |
* @author umbum | |
*/ | |
public class EnumDbValueTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> { | |
private final Map<String, E> enumConstantDirectory; | |
private final Field fieldAnnotatedByDbValue; | |
private final Method methodAnnotatedByDbValue; | |
public DefaultEnumTypeHandler(Class<E> type) { | |
if (type == null) { | |
throw new IllegalArgumentException("Type argument cannot be null"); | |
} | |
E[] universe = type.getEnumConstants(); | |
if (universe == null) { | |
throw new IllegalArgumentException(type.getSimpleName() + " is not an enum type"); | |
} | |
List<Field> fieldsAnnotatedByDbValue = getAnnotatedMembers(type.getDeclaredFields()); | |
List<Method> methodsAnnotatedByDbValue = getAnnotatedMembers(type.getDeclaredMethods()); | |
if (methodsAnnotatedByDbValue.size() + fieldsAnnotatedByDbValue.size() > 1) { | |
throw new IllegalArgumentException(type.getSimpleName() + ": Multiple '@DbValue' properties defined."); | |
} | |
fieldAnnotatedByDbValue = | |
(fieldsAnnotatedByDbValue.size() == 1) ? fieldsAnnotatedByDbValue.get(0) : null; | |
methodAnnotatedByDbValue = | |
(methodsAnnotatedByDbValue.size() == 1) ? methodsAnnotatedByDbValue.get(0) : null; | |
if (fieldAnnotatedByDbValue != null) { | |
fieldAnnotatedByDbValue.setAccessible(true); | |
} | |
enumConstantDirectory = Arrays.stream(universe) | |
.collect(Collectors.toMap(e -> getDbValue(e), e -> e)); | |
} | |
private <T extends Member & AnnotatedElement> List<T> getAnnotatedMembers(T[] members) { | |
return Arrays.stream(members) | |
.filter(field -> field.isAnnotationPresent(DbValue.class)) | |
.collect(Collectors.toList()); | |
} | |
/** | |
여기서 매번 reflection 사용하는 것이 성능 부담이 된다면, Map<E, String> 라는 반대 방향 Map도 가지고 있도록 하면 된다. | |
| 구분 | reflection | map으로 처리 | | |
| --- | --- | --- | | |
| 200,000 건 처리(시간, 메모리 사용량) | 220ms, 25MB | 48ms, 1MB | | |
| 2,000,000 건 처리(시간, 메모리 사용량) | 553ms, 25MB | 196ms, 1MB | | |
| 20,000,000 건 처리(시간, 메모리 사용량) | 4245ms, 182.6MB | 1266ms, 277.1MB | | |
테스트 결과 차이가 크진 않음. | |
*/ | |
private String getDbValue(E e) { | |
try { | |
return fieldAnnotatedByDbValue != null ? (String)fieldAnnotatedByDbValue.get(e) | |
: methodAnnotatedByDbValue != null ? (String)methodAnnotatedByDbValue.invoke(e) | |
: e.name(); | |
} catch (IllegalAccessException | InvocationTargetException ex) { | |
throw new IllegalStateException("Unexpected reflection exception", ex); | |
} | |
} | |
@Override | |
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException { | |
if (jdbcType == null) { | |
ps.setString(i, getDbValue(parameter)); | |
} else { | |
ps.setObject(i, getDbValue(parameter), jdbcType.TYPE_CODE); // see r3589 | |
} | |
} | |
@Override | |
public E getNullableResult(ResultSet rs, String columnName) throws SQLException { | |
String s = rs.getString(columnName); | |
return s == null ? null : enumConstantDirectory.get(s); | |
} | |
@Override | |
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException { | |
String s = rs.getString(columnIndex); | |
return s == null ? null : enumConstantDirectory.get(s); | |
} | |
@Override | |
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { | |
String s = cs.getString(columnIndex); | |
return s == null ? null : enumConstantDirectory.get(s); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.lang.annotation.Documented; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
/** | |
* The annotation that specify property of enum to be used for database I/O. | |
* See also: {@link EnumDbValueTypeHandler} | |
* <p> | |
* <b>How to use:</b> | |
* <pre> | |
* public enum ExampleEnum { | |
* EX1,EX2; | |
* | |
* {@literal @DbValue} | |
* public String getDbCode() { | |
* return this.name().toLowerCase(); | |
* } | |
* } | |
* </pre> | |
* @author umbum | |
*/ | |
@Retention(RetentionPolicy.RUNTIME) | |
@Target({ElementType.METHOD, ElementType.FIELD}) | |
public @interface DbValue { | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment