https://github.com/zhongchengyi/zhongcy.demos/tree/master/mongo-morphia-demo
morphia是java 使用orm方式操作mongodb的一个库。但是默认情况下,使用morphia存取enum时,是按名字存取的。而我们需要把enum按照值存取。
如图:schoolClassLevel1字段是默认的按enum的name进行存取的,schoolClassLevel是我们想要的(按值存取)。
初始化 morphia
Morphia morphia = new Morphia();
try {
Converters converters = morphia.getMapper().getConverters();
Method getEncoder = Converters.class.getDeclaredMethod("getEncoder", Class.class);
getEncoder.setAccessible(true);
TypeConverter enco = ((TypeConverter) getEncoder.invoke(converters, SchoolClassLevel.class));
converters.removeConverter(enco);
converters.addConverter(new EnumOrginalConverter());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
其中, EnumOrginalConverter.java
package zhongcy.demos.converter;
import dev.morphia.converters.SimpleValueConverter;
import dev.morphia.converters.TypeConverter;
import zhongcy.demos.util.EnumOriginalProvider;
import zhongcy.demos.util.EnumUtil;
public class EnumOrginalConverter extends TypeConverter implements SimpleValueConverter {
@Override
@SuppressWarnings({"unchecked", "deprecation"})
public Object decode(final Class targetClass, final Object fromDBObject, final dev.morphia.mapping.MappedField optionalExtraInfo) {
if (fromDBObject == null) {
return null;
}
if (hasEnumOriginalProvider(targetClass)) {
return EnumUtil.getEnumObject(Long.parseLong(fromDBObject.toString()), targetClass);
}
return Enum.valueOf(targetClass, fromDBObject.toString());
}
@Override
@SuppressWarnings({"unchecked", "deprecation"})
public Object encode(final Object value, final dev.morphia.mapping.MappedField optionalExtraInfo) {
if (value == null) {
return null;
}
if (hasEnumOriginalProvider(value.getClass())) {
return ((EnumOriginalProvider) value).getIdx();
}
return getName(((Enum) value));
}
private boolean hasEnumOriginalProvider(Class clzz) {
Class<?>[] interfaces = clzz.getInterfaces();
if (interfaces.length < 1) {
return false;
}
if (interfaces.length == 1) {
return interfaces[0] == EnumOriginalProvider.class;
} else {
for (Class<?> it : interfaces) {
if (it == EnumOriginalProvider.class) {
return true;
}
}
return false;
}
}
@Override
@SuppressWarnings({"unchecked", "deprecation"})
protected boolean isSupported(final Class c, final dev.morphia.mapping.MappedField optionalExtraInfo) {
return c.isEnum();
}
private <T extends Enum> String getName(final T value) {
return value.name();
}
}
EnumOriginalProvider.java
package zhongcy.demos.util;
/**
* enum 的原始数据提供
*/
public interface EnumOriginalProvider {
default String getName() {
return null;
}
long getIdx();
}
EnumUtil.java
package zhongcy.demos.util;
import org.apache.calcite.linq4j.Linq4j;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class EnumUtil {
private static EnumUtil instance = new EnumUtil();
Impl impl;
EnumUtil() {
impl = new Impl();
}
/**
* * 获取value返回枚举对象
* * @param value name 或 index
* * @param clazz 枚举类型
* *
*/
public static <T extends EnumOriginalProvider> T getEnumObject(long idx, Class<T> clazz) {
return instance.impl.getEnumObject(idx, clazz);
}
public static <T extends EnumOriginalProvider> T getEnumObject(String name, Class<T> clazz) {
return instance.impl.getEnumObject(name, clazz);
}
private class Impl {
private Map<Class, EnumFeature[]> enumMap;
public Impl() {
enumMap = new HashMap<>();
}
public <T extends EnumOriginalProvider> T getEnumObject(long value, Class<T> clazz) {
if (!enumMap.containsKey(clazz)) {
enumMap.put(clazz, createEnumFeatures(clazz));
}
try {
EnumFeature first = Linq4j.asEnumerable(enumMap.get(clazz))
.firstOrDefault(f -> value == f.getIndex());
if (first != null) {
return (T) first.getEnumValue();
}
} catch (Exception e) {
}
return null;
}
public <T extends EnumOriginalProvider> T getEnumObject(String value, Class<T> clazz) {
if (!enumMap.containsKey(clazz)) {
enumMap.put(clazz, createEnumFeatures(clazz));
}
try {
EnumFeature first = Linq4j.asEnumerable(enumMap.get(clazz))
.firstOrDefault(f -> value.equals(f.getName()) || f.getEnumValue().toString().equals(value));
if (first != null) {
return (T) first.getEnumValue();
}
} catch (Exception e) {
}
return null;
}
@SuppressWarnings("JavaReflectionInvocation")
private <T extends EnumOriginalProvider> EnumFeature[] createEnumFeatures(Class<T> cls) {
Method method = null;
try {
method = cls.getMethod("values");
return Linq4j.asEnumerable((EnumOriginalProvider[]) method.invoke(null, (Object[]) null))
.select(s -> new EnumFeature(s, s.getName(), s.getIdx())).toList().toArray(new EnumFeature[0]);
} catch (Exception e) {
e.printStackTrace();
return new EnumFeature[0];
}
}
}
private class EnumFeature {
Object enumValue;
String name;
long index;
public EnumFeature(Object enumValue, String name, long index) {
this.enumValue = enumValue;
this.name = name;
this.index = index;
}
public Object getEnumValue() {
return enumValue;
}
public String getName() {
return name;
}
public long getIndex() {
return index;
}
}
}
通过 dev.morphia.DataStoreImpl 的save方法,一路跟踪
到这一步后,查看 TypeConverter 的实现,
找到 EnumConverter,可以看到,morphia 在编码 enum 时,使用的是 enum.getname,我们就想办法替换这个converter.
package dev.morphia.converters;
import dev.morphia.mapping.MappedField;
/**
* @author Uwe Schaefer, (us@thomas-daily.de)
* @author scotthernandez
*/
public class EnumConverter extends TypeConverter implements SimpleValueConverter {
@Override
@SuppressWarnings("unchecked")
public Object decode(final Class targetClass, final Object fromDBObject, final MappedField optionalExtraInfo) {
if (fromDBObject == null) {
return null;
}
return Enum.valueOf(targetClass, fromDBObject.toString());
}
@Override
public Object encode(final Object value, final MappedField optionalExtraInfo) {
if (value == null) {
return null;
}
return getName((Enum) value);
}
@Override
protected boolean isSupported(final Class c, final MappedField optionalExtraInfo) {
return c.isEnum();
}
private <T extends Enum> String getName(final T value) {
return value.name();
}
}
View Code
查看morphia 的接口,获取 Converters 的方法如下
Converters converters = morphia.getMapper().getConverters();
但是,Converters的接口里面,相关的方法都不是 public..
查看 Converters 的初始化,也发现,初始化路径很长,也不提供设置一个自定义的Converters子类。所以,采取了反射方法,找到enumConvert, 替换成支持返回值得Converter。
即:
Converters converters = morphia.getMapper().getConverters();
Method getEncoder = Converters.class.getDeclaredMethod("getEncoder", Class.class);
getEncoder.setAccessible(true);
TypeConverter enco = ((TypeConverter) getEncoder.invoke(converters, SchoolClassLevel.class));
converters.removeConverter(enco);
converters.addConverter(new EnumOrginalConverter());
View Code
实现就比较简单了,通过判断enum是否有指定的 interface(EnumOriginalProvider),如果有,encode 方法就返回值。
代码见 上面(EnumOrginalConverter)
源码定义了两个枚举:
最终数据库 SchoolClassLevel为值,SchoolClassLevel1为name