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是我们想要的(按值存取)。
核心代码
初始化 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(); } }
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());
enumorginalconverter的实现
实现就比较简单了,通过判断enum是否有指定的 interface(enumoriginalprovider),如果有,encode 方法就返回值。
源码定义了两个枚举:
最终数据库 schoolclasslevel为值,schoolclasslevel1为name
源码
其他
- morphia: https://github.com/morphiaorg/morphia
- 快速入门: