源码

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;
        }
    }
}

 morphia简单分析

通过 dev.morphia.DataStoreImpl 的save方法,一路跟踪

 到这一步后,查看 TypeConverter 的实现,

 找到 EnumConverter,可以看到,morphia 在编码 enum 时,使用的是 enum.getname,我们就想办法替换这个converter.

package dev.morphia.converters;


import dev.morphia.mapping.MappedField;


/**
 * @author Uwe Schaefer, ([email protected])
 * @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

 Converter替换

查看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

EnumOrginalConverter的实现

实现就比较简单了,通过判断enum是否有指定的 interface(EnumOriginalProvider),如果有,encode 方法就返回值。

代码见

源码定义了两个枚举:

最终数据库 SchoolClassLevel为值,SchoolClassLevel1为name

其他

02-11 08:37