mybatis自定义枚举类型的转换器以及各种使用场景

mybatis自定义枚举类型的转换器以及各种使用场景

  • 1. 背景
  • 2. 前期准备 + 问题
    • 2.1 创建枚举 SexEnum
    • 2.2 出现的问题
  • 3. 自定义——枚举类型转换器
    • 3.1 IEnum.java
    • 3.2 MybatisEnumHandler.java
    • 3.3 MainConfig.java
    • 3.4 使用注意
    • 3.5 参考地址
  • 4. 使用——自定义的枚举类型转换器
    • 4.1 对于查询
      • 4.1.1 给前端返回的 json
        • 1. 如果不对json做处理
        • 2. 处理返回的json(对枚举属性项的返回)
          • (1)不解析枚举的情况
          • (2)解析枚举的情况
            • 解决问题——解析枚举
        • 3. 修改后的代码
      • 4.1.2 接收前端的查询条件
        • 1. 如果dto新实体(属性都是String)
        • 2. 如果一直用同一个实体DogEntity
    • 4.2 对于新增
      • 4.2.1 对于接收前端传来的参数值
      • 4.2.2 对于xml中设定的默认值
    • 4.3 对于修改
    • 4.4 指定 typeHandler(麻烦、不必须)
  • 5. 附代码

1. 背景

  • 为什么使用枚举?
    mybatis自定义枚举类型的转换器以及各种使用场景_第1张图片
    像性别、四季(春夏秋冬)、星期等,这些固定不变的一组信息我们一般使用枚举,这样我们定义枚举类后,在数据存储方面节省了数据库的存储空间,同时也增强了后台代码的可读性
  • 为什么需要枚举转换器?
    mybatis自定义枚举类型的转换器以及各种使用场景_第2张图片
    看这个给前端返回的json,dogSex的值是0,如果这样给前端返过去,肯定会让前端崩溃吧,你总不能让前端页面展示个0吧,还是你想让前端自己处理0把0变成汉字女展示?,所以属性枚举就来了,问题也就来了,怎么用?

2. 前期准备 + 问题

2.1 创建枚举 SexEnum

  • ① 创建SexEnum.java,如下(下面会改,所以此处不贴代码,直接上图了):
    mybatis自定义枚举类型的转换器以及各种使用场景_第3张图片
  • ② 修改属性类型:
    mybatis自定义枚举类型的转换器以及各种使用场景_第4张图片
    则会个时候,如果你以为完事了,那肯定是你再开玩笑!

2.2 出现的问题

  • 没有类型转换器的话,肯定会报下面的问题
    com.liu.susu.enums.SexEnum cannot be cast to com.liu.susu.enums.config.IEnum
    

3. 自定义——枚举类型转换器

  • 主要3个文件:
    mybatis自定义枚举类型的转换器以及各种使用场景_第5张图片

3.1 IEnum.java

  • 代码如下:
    package com.liu.susu.enums.config;
    
    /**
     * @description
     * @Author susu
     **/
    public interface IEnum<E extends Enum<?>, T>  {
        Object getValue();
        String getName();
    }
    

3.2 MybatisEnumHandler.java

  • 代码如下:
    package com.liu.susu.enums.config.handler;
    
    import cn.hutool.core.util.ClassUtil;
    import com.liu.susu.enums.SexEnum;
    import com.liu.susu.enums.config.IEnum;
    import org.apache.ibatis.type.BaseTypeHandler;
    import org.apache.ibatis.type.JdbcType;
    import org.apache.ibatis.type.MappedTypes;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.Map;
    import java.util.Set;
    
    /**
     * @FileName MybatisEnumHandler
     * @Description  枚举类型处理器
     *               自动将实体类中类型为 MyEnum 子类的属性,转换为枚举
     * @Author susu
     **/
    
    //此注解告诉 mybatis 遇到此 SexEnum.class类型时,使用此处理器处理
    @MappedTypes(value = {SexEnum.class})
    public class MybatisEnumHandler<E extends Enum<E> & IEnum> extends BaseTypeHandler<E> {
    
        public static String enumPackage;
    
        /**
         * 动态修改@MappedTypes(value = {})的value值
         * 原理:类的注解,是动态代理生成的类,通过获取代理对象,反射修改其值
         */
        static {
            try {
                MappedTypes annotation = MybatisEnumHandler.class.getAnnotation(MappedTypes.class);
                InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
                Field memberValues = invocationHandler.getClass().getDeclaredField("memberValues");
                memberValues.setAccessible(true);
    //            Map values = (Map) memberValues.get(invocationHandler);
    //            Set> classes = ClassUtil.scanPackageBySuper(enumPackage, IEnum.class);
    //            Class[] allMyEnums = classes.toArray(new Class[classes.size()]);
    //            values.put("value", allMyEnums);
    
                /**
                 * classes.size() > 0 判断一下,不然如果没有一个枚举继承 IEnum,启动报错
                 */
                Set<Class<?>> classes = ClassUtil.scanPackageBySuper(enumPackage, IEnum.class);
                Map values = (Map) memberValues.get(invocationHandler);
                if (classes.size() > 0) {
                    Class[] allMyEnums = classes.toArray(new Class[classes.size()]);
                    values.put("value", allMyEnums);
                }
    
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    
        private final Class<E> type;
    
        /**
         * construct with parameter.
         */
        public MybatisEnumHandler(Class<E> type) {
            if (type == null) {
                throw new IllegalArgumentException("Type argument cannot be null");
            }
            this.type = type;
        }
    
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType)
                throws SQLException {
    //        ps.setString(i, parameter.getValue());
            ps.setObject(i, parameter.getValue());
        }
    
        @Override
        public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
            String code = rs.getString(columnName);
            return rs.wasNull() ? null : this.codeOf(this.type, code);
        }
    
        @Override
        public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            String code = rs.getString(columnIndex);
            return rs.wasNull() ? null : this.codeOf(this.type, code);
        }
    
        @Override
        public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            String code = cs.getString(columnIndex);
            return cs.wasNull() ? null : this.codeOf(this.type, code);
        }
    
        public <T extends Enum<?> & IEnum> T codeOf(Class<T> enumClass, String code) {
            T[] enumConstants = enumClass.getEnumConstants();
            for (T t : enumConstants) {
                if (t.getValue().equals(code)) {
                    return t;
                }
            }
            return null;
        }
    }
    
    

3.3 MainConfig.java

  • 代码如下:
    package com.liu.susu.enums.config;
    
    import com.liu.susu.enums.config.handler.MybatisEnumHandler;
    import org.apache.ibatis.type.TypeHandler;
    import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.util.StringUtils;
    
    /**
     * @FileName MainConfig
     * @Description  MybatisProperties后置处理器,配置MyBatis的枚举处理器typeHandle
     *               相当用户在yml中配置:  mybatis.type-handlers-package
     * @Author susu
     **/
    @Configuration
    @ConditionalOnClass({TypeHandler.class, MybatisProperties.class})
    public class MainConfig implements BeanPostProcessor {
    
        @Value("${susu.mybatis.enumPackage:null}")
        String enumPackage;
    
        public static final String HANDLER_PACKAGE = "com.liu.susu.enums.config.handler";
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof MybatisProperties) {
                if (!StringUtils.hasText(enumPackage)) {
                    enumPackage = getMainClassPackage();
                }
                MybatisEnumHandler.enumPackage = enumPackage;
                MybatisProperties properties = (MybatisProperties) bean;
                String src = properties.getTypeHandlersPackage();
                if (src != null) {
                    // mybatis.type-handlers-package可以接受多个路径,分隔符有多种,英文逗号为其中一种
                    src += "," + HANDLER_PACKAGE;
                } else {
                    src = HANDLER_PACKAGE;
                }
                properties.setTypeHandlersPackage(src);
            }
            return bean;
        }
    
        /**
         * 获取main方法启动类所在的包
         * @return
         */
        public static String getMainClassPackage() {
            StackTraceElement[] stackTraceElements = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTraceElements) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    String mainClassName = stackTraceElement.getClassName();
                    return mainClassName.substring(0, mainClassName.lastIndexOf("."));
                }
            }
            return "";
        }
    }
    
    

3.4 使用注意

  • 有几个小的注意点,直接看图吧:
    ① 创建的枚举需要 implements IEnum
    mybatis自定义枚举类型的转换器以及各种使用场景_第6张图片
    ② 让枚举使用自己的类型处理器
    mybatis自定义枚举类型的转换器以及各种使用场景_第7张图片
    ③ 注意包路径修改成自己的:
    mybatis自定义枚举类型的转换器以及各种使用场景_第8张图片

3.5 参考地址

  • 上面自定义枚举类型转换器的代码参考github上的大佬的代码,拿来简单修改即可用,想直接下载的根据下面的链接:
    https://github.com/haerxiong/MybatisAutoEnum.
  • 好了,接下来就是crud的应用了,简单都说说吧,需要的请继续……

4. 使用——自定义的枚举类型转换器

4.1 对于查询

4.1.1 给前端返回的 json

1. 如果不对json做处理

  • 直接先看图:
    mybatis自定义枚举类型的转换器以及各种使用场景_第9张图片
    如果不处理,返回的是 GIRL,这不是我们想要的结果,我们希望给前端返回是汉字”女孩“
    mybatis自定义枚举类型的转换器以及各种使用场景_第10张图片

2. 处理返回的json(对枚举属性项的返回)

(1)不解析枚举的情况
  • 对于上面的情况,我这边的处理是,给 SexEnum.java 加toString()方法 + SerializerFeature.WriteEnumUsingToString,如下:
    ① 直接 toString() 先试试:
    mybatis自定义枚举类型的转换器以及各种使用场景_第11张图片
    mybatis自定义枚举类型的转换器以及各种使用场景_第12张图片
    看看效果:
    mybatis自定义枚举类型的转换器以及各种使用场景_第13张图片
    这个看着没毛病,但是有的多余,关键是 json 里面有”=“,对前端来说json格式不对,解析困难,所以我们只需要把toString()方法改一下即可
    ② 改后,如图:
    mybatis自定义枚举类型的转换器以及各种使用场景_第14张图片
    mybatis自定义枚举类型的转换器以及各种使用场景_第15张图片
    好了,简单粗暴,但完美解决!
(2)解析枚举的情况
  • 如果你觉得上面真的是完美解决,那就真的错了,上面枚举没报错,那是真的巧合,接下来我们换个枚举的key试试,不用0和1,用01,02试试,问题就出现了
    mybatis自定义枚举类型的转换器以及各种使用场景_第16张图片
    mybatis自定义枚举类型的转换器以及各种使用场景_第17张图片
    报错日志如下:
    在这里插入图片描述
    Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `com.liu.susu.enums.SexEnum` from String "02": not one of the values accepted for Enum class: [GIRL, BOY]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `com.liu.susu.enums.SexEnum` from String "02": not one of the values accepted for Enum class: [GIRL, BOY]
     at [Source: (PushbackInputStream); line: 1, column: 24] (through reference chain: com.liu.susu.pojo.entity.DogEntity["dogSex"])]
    
解决问题——解析枚举
  • 对于上面的问题,就需要我们再接收json时,对枚举进行解析
  • 代码如下:
    ① EnumUtils .java
    package com.liu.susu.enums.utils;
    
    import com.liu.susu.enums.config.IEnum;
    import lombok.extern.slf4j.Slf4j;
    
    import java.lang.reflect.Method;
    
    /**
     * description
     * @author susu
     **/
    @Slf4j
    public class EnumUtils {
    
        public static <T extends Enum<T>> T valueOf(Class<T> enumType, Object value) {
            try {
                Method method = enumType.getMethod("values");
                Enum[] enums = (Enum[])method.invoke(enumType);
    
                for(int i = 0; i < enums.length; i++) {
                    T e = (T) enums[i];
                    if (((IEnum)e).getValue().equals(value)) {
                        return e;
                    }
                }
                return null;
            } catch (Exception e) {
                log.error("method values is not found in " + enumType.getClass().getName(), e);
                return null;
            }
        }
        
    }
    

② MyEnumDeserializer.java

package com.liu.susu.enums.external;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.liu.susu.enums.SexEnum;
import com.liu.susu.enums.utils.EnumUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.io.IOException;

/**
 * description 解析枚举用
 * @author susu
 **/
@Slf4j
public class MyEnumDeserializer extends JsonDeserializer<Enum<? extends Enum<?>>> {
    public MyEnumDeserializer() {
    }

    public Enum<? extends Enum<?>> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        try {
            String enumValue = jsonParser.getText();
            if (StringUtils.isNotEmpty(enumValue)){
                return getMyEnum(jsonParser.getCurrentName(), jsonParser.getText());
            }else {
                return null;
            }
        } catch (Exception var5) {
            throw new RuntimeException("枚举解析错误,字段属性为:" + jsonParser.getCurrentName()
                    + ", 传的枚举value为:" + jsonParser.getText());
        }
    }

    /**
    * description :根据实体属性 和 接收到的值,返回对应的枚举
    */
    public Enum<? extends Enum<?>> getMyEnum(String propertyKey, String enumKey) {
        return EnumUtils.valueOf(SexEnum.class, enumKey);
    }

}

③ 实体加注解:@JsonDeserialize(using = MyEnumDeserializer.class)
mybatis自定义枚举类型的转换器以及各种使用场景_第18张图片

  • 效果图:
    mybatis自定义枚举类型的转换器以及各种使用场景_第19张图片

3. 修改后的代码

  • ① 枚举
    package com.liu.susu.enums;
    
    import com.liu.susu.enums.config.IEnum;
    
    /**
    * @Description:
    * @Author susu
    */
    public enum SexEnum  implements IEnum {
    
        GIRL("01","女孩"),
        BOY("02","男孩");
    
        private String value;
        private String name;
    
        SexEnum(String value, String name) {
            this.value = value;
            this.name = name;
        }
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return name;
        }
        
    }
    
    
  • ② mapper.xml文件
    mybatis自定义枚举类型的转换器以及各种使用场景_第20张图片
  • ③ controller
    package com.liu.susu.controller.pet;
    
    import com.alibaba.fastjson.JSONObject;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    import com.liu.susu.service.pet.DogService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    /**
     * description 狗狗增删改查,领养等业务处理
     * @author susu
     **/
    @Controller
    @RequestMapping("/pet/dog")
    @Slf4j
    public class DogController {
    
        @Autowired
        private DogService dogService;
        
        @ResponseBody
        @GetMapping("/selectAllDogs")
        public String selectAllDogs(){
            return JSONObject.toJSONString(dogService.selectAllDogs(),
                    SerializerFeature.WriteEnumUsingToString);
        }
    
    }
    

4.1.2 接收前端的查询条件

1. 如果dto新实体(属性都是String)

  • 这个接收参数就无所谓了,前端给啥就接啥就行了,这个不说了,关键是下面的

2. 如果一直用同一个实体DogEntity

  • 如果你的controller参数是DogEntity(属性是枚举类型的实体),需要简单注意一点点(.value别忘了就ok,接收到前端的数据也不用处理,会自动解析的,把数字解析成枚举)
    mybatis自定义枚举类型的转换器以及各种使用场景_第21张图片
    mybatis自定义枚举类型的转换器以及各种使用场景_第22张图片

  • 看一下效果:
    mybatis自定义枚举类型的转换器以及各种使用场景_第23张图片

4.2 对于新增

4.2.1 对于接收前端传来的参数值

  • 这个同查询是一样的,直接给图了
    mybatis自定义枚举类型的转换器以及各种使用场景_第24张图片
    mybatis自定义枚举类型的转换器以及各种使用场景_第25张图片
    mybatis自定义枚举类型的转换器以及各种使用场景_第26张图片
    mybatis自定义枚举类型的转换器以及各种使用场景_第27张图片

4.2.2 对于xml中设定的默认值

4.3 对于修改

  • 到这了,没必要说了吧……

4.4 指定 typeHandler(麻烦、不必须)

  • 可以在xml文件里指定,但是没必要(除非不生效的情况,可以试试这种情况)
    比如新增可以用下面的方式:
    mybatis自定义枚举类型的转换器以及各种使用场景_第31张图片
    
    <insert id="insertDog" parameterType="com.liu.susu.pojo.entity.DogEntity">
        insert into dog (dog_name,dog_age,dog_sex)
        values
        (
        #{dogName,jdbcType=VARCHAR},
        #{dogAge,jdbcType=INTEGER},

        #{dogSex,jdbcType=VARCHAR,
        javaType=com.liu.susu.enums.SexEnum,
        typeHandler=com.liu.susu.enums.config.handler.MybatisEnumHandler}

        )
    insert>

5. 附代码

  • 项目下载地址:
    1.springbood+mybatis项目demo 2.mybatis自定义枚举类型的转换器以及各种使用场景

你可能感兴趣的:(#,MySQL,and,mybatis,mybatis,java,mysql,mybatis自定义枚举转换器)