Skip to content

Instantly share code, notes, and snippets.

@umbum
Last active November 23, 2021 04:58
Show Gist options
  • Save umbum/492c839c2ad5a9d09f638369b481f9c8 to your computer and use it in GitHub Desktop.
Save umbum/492c839c2ad5a9d09f638369b481f9c8 to your computer and use it in GitHub Desktop.
MyBatis Custom DefaultEnumTypeHandler using annotation
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>
* &lt;setting name="defaultEnumTypeHandler" value="~~~.~~~.EnumDbValueTypeHandler"/&gt;
* </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);
}
}
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