欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java 使用 morphia 存取枚举为值

程序员文章站 2022-06-05 17:34:25
前言 morphia是java 使用orm方式操作mongodb的一个库。但是默认情况下,使用morphia存取enum时,是按名字存取的。而我们需要把enum按照值存取。 如图:schoolClassLevel1字段是默认的按enum的name进行存取的,schoolClassLevel是我们想要 ......

前言

morphia是java 使用orm方式操作mongodb的一个库。但是默认情况下,使用morphia存取enum时,是按名字存取的。而我们需要把enum按照值存取。

如图:schoolclasslevel1字段是默认的按enum的name进行存取的,schoolclasslevel是我们想要的(按值存取)。

java 使用 morphia 存取枚举为值

 核心代码

初始化 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方法,一路跟踪

java 使用 morphia 存取枚举为值

 

 java 使用 morphia 存取枚举为值

 

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

java 使用 morphia 存取枚举为值

 

 找到 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();
    }
}

 converter替换

查看morphia 的接口,获取 converters 的方法如下

converters converters = morphia.getmapper().getconverters();

但是,converters的接口里面,相关的方法都不是 public..

java 使用 morphia 存取枚举为值

 

 

 查看 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());

enumorginalconverter的实现

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

代码见上面(enumorginalconverter)

源码定义了两个枚举:

java 使用 morphia 存取枚举为值

java 使用 morphia 存取枚举为值

 

最终数据库 schoolclasslevel为值,schoolclasslevel1为name

java 使用 morphia 存取枚举为值

 

源码

其他