• java 使用 morphia 存取枚举为值


     源码

     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, (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

     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 方法就返回值。

    代码见上面(EnumOrginalConverter)

    源码定义了两个枚举:

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

     其他

      

  • 相关阅读:
    安卓自动化测试添加用例执行回放
    【十二省2019】异或粽子
    【BZOJ4260】Codechef REBXOR
    【JSOI2015】字符串树
    【HAOI2017】供给侧改革
    【NOI2018】你的名字
    【十二省2019】字符串问题
    【LOJ#6041】事情的相似度
    【SP8093】JZPGYZ
    【BZOJ1396】识别子串
  • 原文地址:https://www.cnblogs.com/zhongchengyi/p/12125014.html
Copyright © 2020-2023  润新知