一般我们在项目中,设置表的类型字段,都是用int存储,用0、1、2…代表不同类型的含义。
以性别字段为例:
数据库表字段设计为integer:
CREATE TABLE t_user_info ( ... sex INT NOT NULL COMMENT '性别,0-女,1-男' ... );
实体成员变量设计为枚举:
public class UserInfoEntity extends BaseField { ... /** 性别,0-女,1-男 */ private Sex sex; ... } @Getter public enum Sex implements BaseEnum { woman(0, "女"), man(1, "男"); @EnumValue //标记数据库存的值是sex private Integer code; private String desc; Sex(int code, String desc) { this.code = code; this.desc = desc; } public String getStrCode() { return code.toString(); } }
如果我们不做任何额外处理,当使用Sex枚举作为出入参时。只能返回man或woman或用man或woman作为参数。
封装出参实体中sex成员变量类型为Sex枚举,则出参为:man或woman
若是使用Sex枚举类型接收请求参数,不论是从接口get请求url中获取,还是使用构造实体接收post请求参数。都只能用man或woman作为参数,如果使用0或1作为参数,则无法正确被转换为Sex枚举。但是在开发过程中前端下拉选选项一般都与0,1,2…相对应。
以上面的Sex枚举为例子,我希望在返回sex字段时,用0和1作为返回值,并且希望能用0和1映射男和女作为接口入参。该如何实现呢?
/** * 枚举基础接口 * 注意:不要构造枚举name和code相互交叉使用的枚举,否则在进行转换时将获取意外的结果。例如: *public enum Sex2 implements BaseEnum { * woman("man", "女"), man("woman", "男"); * * private String code; * private String desc; * * Sex2(String code, String desc) { * this.code = code; * this.desc = desc; * } * public String getStrCode() { * return code; * } * } * * 若是这种则是可以的:woman("woman", "女"), man("man", "男"); */ public interface BaseEnum { /** * 根据枚举值或名称从枚举类型type中获取枚举对象 */ public staticT getEnum(Class type, Object codeOrName) { T[] enums = type.getEnumConstants(); for (T em : enums) { if (em.getStrCode().equals(codeOrName.toString()) || em.name().equals(codeOrName.toString())) { return em; } } return null; } /** * 获取枚举值的字符串code * * @return 编码 */ String getStrCode(); /** * 获取枚举名称 * * @return 名称 */ String name(); }
import com.baomidou.mybatisplus.annotation.EnumValue; import lombok.Getter; @Getter public enum Sex implements BaseEnum { woman(0, "女"), man(1, "男"); @EnumValue //标记数据库存的值是sex private Integer code; private String desc; Sex(int code, String desc) { this.code = code; this.desc = desc; } public String getStrCode() { return code.toString(); } }
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.study.enums.BaseEnum; import java.io.IOException; /** * BaseEnum 序列化 */ public class BaseEnumSerializer extends JsonSerializer{ @Override public void serialize(BaseEnum value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeNumber(value.getStrCode()); // 增加一个字段,格式为【枚举类名称+Text】,存储枚举的name gen.writeStringField(gen.getOutputContext().getCurrentName() + "Text", value.name()); } }
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.study.enums.BaseEnum; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import java.io.IOException; /** * BaseEnum反序列化 */ @Slf4j public class BaseEnumDeserializer extends JsonDeserializer{ @Override public Enum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.getCodec().readTree(p); String currentName = p.currentName(); Object currentValue = p.getCurrentValue(); Class findPropertyType = BeanUtils.findPropertyType(currentName, currentValue.getClass()); if (findPropertyType == null) { log.info("在" + currentValue.getClass() + "实体类中找不到" + currentName + "字段"); return null; } String asText = node.asText(); if (StringUtils.isBlank(asText)) { return null; } if (BaseEnum.class.isAssignableFrom(findPropertyType)) { BaseEnum valueOf = null; if (StringUtils.isNotBlank(asText)) { valueOf = BaseEnum.getEnum(findPropertyType, asText); } if (valueOf != null) { return (Enum) valueOf; } } return Enum.valueOf(findPropertyType, asText); } }
配置Jackson在序列化BaseEnum和反序列化Enum时,使用我们自己定义的BaseEnumSerializer、EnumDeserializer进行处理。
因为Spring boot使用的是Jackson序列化接口出参的,所以若我们自定定义的枚举若是都实现了BaseEnum接口,那么就会调用BaseEnumSerializer序列化枚举对象,达到预期的效果。
package com.study.config; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.study.enums.BaseEnum; import com.study.serializer.EnumDeserializer; import com.study.serializer.BaseEnumSerializer; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.TimeZone; /** * 加载baseEnum 构建bean定义,初始化Spring容器。 */ @Configuration public class JacksonConfig { // JacksonConfig EnumBeanConfig @Bean public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() { BaseEnumSerializer baseEnumSerializer = new BaseEnumSerializer(); EnumDeserializer enumDeserializer = new EnumDeserializer(); return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()) // 配置Jackson在序列化BaseEnum和反序列化Enum时,使用我们自己定义的BaseEnumSerializer、EnumDeserializer进行处理 .serializerByType(BaseEnum.class, baseEnumSerializer) .deserializerByType(Enum.class, enumDeserializer); } }
作用是当我们使用枚举类型接收接口入参时,会根据请求参数是Integer或者String调用各自的转换器转为BaseEnum枚举类型
import com.study.converters.IntegerToEnumConverter; import com.study.enums.BaseEnum; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; import java.util.HashMap; import java.util.Map; /** * 枚举 转化器工厂类 * 将Integer类型的参数转换为BaseEnum类型枚举对象 * 参考:org.springframework.core.convert.support.IntegerToEnumConverterFactory */ public class IntegerCodeToEnumConverterFactory implements ConverterFactory{ private static final Map CONVERTERS = new HashMap<>(); /** * 获取一个从 Integer 转化为 T 的转换器,T 是一个泛型,有多个实现 * * @param targetType 转换后的类型 * @return 返回一个转化器 */ @Override public Converter getConverter(Class targetType) { Converter converter = CONVERTERS.get(targetType); if (converter == null) { converter = new IntegerToEnumConverter<>(targetType); CONVERTERS.put(targetType, converter); } return converter; } }
import cn.hutool.core.util.ObjectUtil; import com.study.enums.BaseEnum; import org.springframework.core.convert.converter.Converter; import java.util.HashMap; import java.util.Map; /** * Integer类型参数枚举 转化器 * * @param*/ public class IntegerToEnumConverter implements Converter { private Map enumMap = new HashMap<>(); public IntegerToEnumConverter(Class enumType) { T[] enums = enumType.getEnumConstants(); for (T e : enums) { enumMap.put(e.getStrCode(), e); } } @Override public T convert(Integer source) { T t = enumMap.get(source); if (ObjectUtil.isNull(t)) { throw new IllegalArgumentException("无法匹配对应的枚举类型"); } return t; } }
import com.study.converters.StringToEnumConverter; import com.study.enums.BaseEnum; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; import java.util.HashMap; import java.util.Map; /** * 枚举 转化器工厂类 * 将String类型的参数转换为BaseEnum类型枚举对象 * 参考:org.springframework.core.convert.support.StringToEnumConverterFactory */ public class StringCodeToEnumConverterFactory implements ConverterFactory{ private static final Map CONVERTERS = new HashMap<>(); /** * 获取一个从 Integer 转化为 T 的转换器,T 是一个泛型,有多个实现 * * @param targetType 转换后的类型 * @return 返回一个转化器 */ @Override public Converter getConverter(Class targetType) { Converter converter = CONVERTERS.get(targetType); if (converter == null) { converter = new StringToEnumConverter<>(targetType); CONVERTERS.put(targetType, converter); } return converter; } }
import cn.hutool.core.util.ObjectUtil; import com.study.enums.BaseEnum; import org.springframework.core.convert.converter.Converter; import java.util.HashMap; import java.util.Map; /** * String类型参数枚举 转化器 * * @param*/ public class StringToEnumConverter implements Converter { private final Map enumMap = new HashMap<>(); public StringToEnumConverter(Class enumType) { T[] enums = enumType.getEnumConstants(); for (T e : enums) { enumMap.put(e.getStrCode(), e); enumMap.put(e.name(), e); } } @Override public T convert(String source) { T t = enumMap.get(source); if (ObjectUtil.isNull(t)) { throw new IllegalArgumentException("无法匹配对应的枚举类型"); } return t; } }
import com.study.converters.factory.IntegerCodeToEnumConverterFactory; import com.study.converters.factory.StringCodeToEnumConverterFactory; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfigurer implements WebMvcConfigurer { /** * 枚举类的转换器工厂 addConverterFactory */ @Override public void addFormatters(FormatterRegistry registry) { registry.addConverterFactory(new IntegerCodeToEnumConverterFactory()); registry.addConverterFactory(new StringCodeToEnumConverterFactory()); } }
使用0、1、man、woman都可以请求接口
接口返回值为0、1。还新增了字段sexText保存枚举的名称
项目gitee链接: https://gitee.com/WillDistance/mybatis-plus-h2.git