【MyBatis源码分析】TypeHandler解析属性配置元素详述及相关枚举使用高级进阶

TypeHandler解析

接着看一下typeHandlerElement(root.evalNode("typeHandlers"));方法,这句读取的是<configuration>下的<typeHandlers>节点,代码实现为:

private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

Mybatis中的TypeHandler是什么?

  无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。Mybatis默认为我们实现了许多TypeHandler, 当我们没有配置指定TypeHandler时,Mybatis会根据参数或者返回结果的不同,默认为我们选择合适的TypeHandler处理。

那么,Mybatis为我们实现了哪些TypeHandler呢?  我们怎么自定义实现一个TypeHandler ?  这些都会在接下来的mybatis的源码中看到。

Mybatis的初始化TypeHandler初始化注册对象为如下代码:

public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());

    register(Byte.class, new ByteTypeHandler());
    register(byte.class, new ByteTypeHandler());
    register(JdbcType.TINYINT, new ByteTypeHandler());

    register(Short.class, new ShortTypeHandler());
    register(short.class, new ShortTypeHandler());
    register(JdbcType.SMALLINT, new ShortTypeHandler());

    register(Integer.class, new IntegerTypeHandler());
    register(int.class, new IntegerTypeHandler());
    register(JdbcType.INTEGER, new IntegerTypeHandler());

    register(Long.class, new LongTypeHandler());
    register(long.class, new LongTypeHandler());

    register(Float.class, new FloatTypeHandler());
    register(float.class, new FloatTypeHandler());
    register(JdbcType.FLOAT, new FloatTypeHandler());

    register(Double.class, new DoubleTypeHandler());
    register(double.class, new DoubleTypeHandler());
    register(JdbcType.DOUBLE, new DoubleTypeHandler());

    register(Reader.class, new ClobReaderTypeHandler());
    register(String.class, new StringTypeHandler());
    register(String.class, JdbcType.CHAR, new StringTypeHandler());
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
    register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
    register(JdbcType.CHAR, new StringTypeHandler());
    register(JdbcType.VARCHAR, new StringTypeHandler());
    register(JdbcType.CLOB, new ClobTypeHandler());
    register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
    register(JdbcType.NVARCHAR, new NStringTypeHandler());
    register(JdbcType.NCHAR, new NStringTypeHandler());
    register(JdbcType.NCLOB, new NClobTypeHandler());

    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
    register(JdbcType.ARRAY, new ArrayTypeHandler());

    register(BigInteger.class, new BigIntegerTypeHandler());
    register(JdbcType.BIGINT, new LongTypeHandler());

    register(BigDecimal.class, new BigDecimalTypeHandler());
    register(JdbcType.REAL, new BigDecimalTypeHandler());
    register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
    register(JdbcType.NUMERIC, new BigDecimalTypeHandler());

    register(InputStream.class, new BlobInputStreamTypeHandler());
    register(Byte[].class, new ByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
    register(byte[].class, new ByteArrayTypeHandler());
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
    register(JdbcType.BLOB, new BlobTypeHandler());

    register(Object.class, UNKNOWN_TYPE_HANDLER);
    register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
    register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);

    register(Date.class, new DateTypeHandler());
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
    register(JdbcType.TIMESTAMP, new DateTypeHandler());
    register(JdbcType.DATE, new DateOnlyTypeHandler());
    register(JdbcType.TIME, new TimeOnlyTypeHandler());

    register(java.sql.Date.class, new SqlDateTypeHandler());
    register(java.sql.Time.class, new SqlTimeTypeHandler());
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

    // mybatis-typehandlers-jsr310
    try {
      // since 1.0.0
      register("java.time.Instant", "org.apache.ibatis.type.InstantTypeHandler");
      register("java.time.LocalDateTime", "org.apache.ibatis.type.LocalDateTimeTypeHandler");
      register("java.time.LocalDate", "org.apache.ibatis.type.LocalDateTypeHandler");
      register("java.time.LocalTime", "org.apache.ibatis.type.LocalTimeTypeHandler");
      register("java.time.OffsetDateTime", "org.apache.ibatis.type.OffsetDateTimeTypeHandler");
      register("java.time.OffsetTime", "org.apache.ibatis.type.OffsetTimeTypeHandler");
      register("java.time.ZonedDateTime", "org.apache.ibatis.type.ZonedDateTimeTypeHandler");
      // since 1.0.1
      register("java.time.Month", "org.apache.ibatis.type.MonthTypeHandler");
      register("java.time.Year", "org.apache.ibatis.type.YearTypeHandler");

    } catch (ClassNotFoundException e) {
      // no JSR-310 handlers
    }

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }
其实大部分需求上面的注册已经满足!接下来我们先看typeHandler相关Xml配置信息:
<typeHandlers>
         <!--javaType 配置java类型,例如String, 如果配上javaType, 那么指定的typeHandler就只作用于指定的类型 -->
        <!--<typeHandler handler="Handler.AddressTypeHandler"  javaType="model.Address" jdbcType="VARCHAR" />-->
        <!--<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"  /> 使用枚举名称作为参数传递-->
        <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"  javaType="enums.web" /><!-- 使用整数下标作为参数传递-->
        <!--<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"  javaType="enums.web" /><!– 使用字符串作为参数传递–>-->
        <!-- 配置package的时候,mybatis会去配置的package扫描TypeHandler -->
        <package name="Handler"/>

    </typeHandlers>

我们来自定义实现一个自定义Handler

假设邮箱可用拼接的方式存储

定义一个邮箱类:

/**
 * Copyright (C), 2015-2018, XXX有限公司
 * FileName: AssembledMail
 * Author:   temp
 * Date:     2018/3/29 13:51
 * Description:
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package model;

/**
 * 邮箱类
 *  组装邮箱<br>
 * 〈〉
 *
 * @author temp
 * @create 2018/3/29
 * @since 1.0.0
 */
public class AssembledMail {

    private String head;

    private String tail;

    public AssembledMail(){

    }

    public AssembledMail(String head,String tail){

        this.head = head;
        this.tail = tail;
    }

    public String getHead() {
        return head;
    }

    public String getTail() {
        return tail;
    }

    public void setHead(String head) {
        this.head = head;
    }

    public void setTail(String tail) {
        this.tail = tail;
    }

    @Override
    public String toString() {
        return head + "@" + tail;
    }
}

继承并实现BaseTypeHandler可实现注册TypeHandler(可配置JavaType及JdbcType)

/**
 * Copyright (C), 2015-2018, XXX有限公司
 * FileName: AddressTypeHandler
 * Author:   temp
 * Date:     2018/3/29 9:58
 * Description:
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package Handler;

import model.AssembledMail;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 〈一句话功能简述〉<br> 
 * 〈〉
 *
 * @author temp
 * @create 2018/3/29
 * @since 1.0.0
 */
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(value = {AssembledMail.class})
public class AddressTypeHandler  extends BaseTypeHandler<AssembledMail> {


    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, AssembledMail address, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i,address.toString());

    }

    @Override
    public AssembledMail getNullableResult(ResultSet resultSet, String s) throws SQLException {

        return get(resultSet.getString(s));
    }

    @Override
    public AssembledMail getNullableResult(ResultSet resultSet, int i) throws SQLException {

        return get(resultSet.getString(i));
    }

    @Override
    public AssembledMail getNullableResult(CallableStatement callableStatement, int i) throws SQLException {

        return get(callableStatement.getString(i));
    }

    private AssembledMail get(String sta){
        AssembledMail assembledMail = new AssembledMail();
        assembledMail.setHead(sta.substring(0, sta.indexOf("@")));
        assembledMail.setTail(sta.substring(sta.indexOf("@")+1,sta.length()));
        return assembledMail;
    }
}

开起xml扫描这个类

xml映射配置

<resultMap type="Mail" id="MailResultMap">
         <result column="id" property="id" />
         <result column="create_time" property="createTime" />
         <result column="modify_time" property="modifyTime" />
         <result column="web_id" property="webId" />
         <result column="mail" property="mail" typeHandler="Handler.AddressTypeHandler"/>
         <result column="use_for" property="useFor" />
     </resultMap>

接下来修改Mail的mail的类型为AssemblerMail类型

接下来我们测试一下

 @Test
	 public void testInsert() {
	    Mail mail1 = new Mail(1, new AssembledMail("ztp","qq.com"), "个人使用");
	    System.out.println(mailDao.insertMail(mail1));
	 }

结果mail这个单个字段已经映射进去

接下来我们查询一下

@Test
	 public void testDelete() {
	     System.out.println(mailDao.deleteMail(12));
	 }

结果符合我们的要求.

实现简单实现自定义Handler后我们按照业务场景实现比较多的枚举映射

前面的xml配置文件已经简单说明了Mybatis自带的针对枚举的映射类区别

我们来实现一下枚举Handler:

定义一个枚举类

/**
 * Copyright (C), 2015-2018, XXX有限公司
 * FileName: web
 * Author:   temp
 * Date:     2018/3/29 11:03
 * Description:
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package enums;

import org.apache.ibatis.type.MappedTypes;

/**
 * 〈一句话功能简述〉<br> 
 * 〈〉
 *
 * @author temp
 * @create 2018/3/29
 * @since 1.0.0
 */
/*@MappedTypes(enums.web.class)*/
public enum  web {

    SING(1,"新浪"),
    QQ(2,"QQ"),
    SOHU(3,"搜狐"),
    FIREFOX(4,"火狐");

    private int id;

    private String name;

    private web(int id,String name){

        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return super.toString();
    }

    public static web getweb(int id){
        if(id == 1){

            return SING;
        }else if(id == 2){

            return QQ;
        }else if(id == 3){

            return SOHU;
        }else if(id == 4){

            return FIREFOX;
        }
        return null;
    }
}

配置属性映射

<resultMap type="Mail" id="MailResultMap">
         <result column="id" property="id" />
         <result column="create_time" property="createTime" />
         <result column="modify_time" property="modifyTime" />
         <result column="web_id" property="webId" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler" />
         <result column="mail" property="mail" typeHandler="Handler.AddressTypeHandler"/>
         <result column="use_for" property="useFor" />
     </resultMap>

接下来修改Mail的webId的类型为枚举web

接下来我们看实际运行效果:

@Test
	 public void testInsert() {
	    Mail mail1 = new Mail(web.FIREFOX, new AssembledMail("ztp","qq.com"), "个人使用");
	    System.out.println(mailDao.insertMail(mail1));
	 }

效果

查询结果

从源码实现中我们可以知道两点,<typeHandlers>标签下可以定义<package>和<typeHandler>两种标签,但是看第4行和第7行的判断,这是一段if...else...,因此可以知道<package>标签和<typeHandler>标签只能定义其中的一种

接下来我们继续看到6行代码:

public void register(String packageName) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
    Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
    for (Class<?> type : handlerSet) {
      //Ignore inner classes and interfaces (including package-info.java) and abstract classes
      if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
        register(type);
      }
    }
  }
第3行根据路径packageName寻找它下面的".class"文件拿到所有的".class"文件对应的类的Class,然后遍历所有的Class,做了三层判断
  • 必须不是匿名类
  • 必须不是接口
  • 必须不是抽象成员类
此时此Class对应的类符合条件,会进行注册,通过register方法进行注册,看一下方法实现:

public void register(Class<?> typeHandlerClass) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> javaTypeClass : mappedTypes.value()) {
        register(javaTypeClass, typeHandlerClass);
        mappedTypeFound = true;
      }
    }
    if (!mappedTypeFound) {
      register(getInstance(null, typeHandlerClass));
    }
  }
第3行获取类上面的注解MappedTypes,如果MappedTypes注解中有定义value属性且指定了对象的class,那么第4行~第7行的判断优先取这个Class。

我们继续看第6行:

public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
  }
方法重载,我们看一下getInstance方法:

public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    if (javaTypeClass != null) {
      try {
        Constructor<?> c = typeHandlerClass.getConstructor(Class.class);
        return (TypeHandler<T>) c.newInstance(javaTypeClass);
      } catch (NoSuchMethodException ignored) {
        // ignored
      } catch (Exception e) {
        throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
      }
    }
    try {
      Constructor<?> c = typeHandlerClass.getConstructor();
      return (TypeHandler<T>) c.newInstance();
    } catch (Exception e) {
      throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
    }
  }
通过反射它的泛型,来知道你想用这个TypeHandler处理的java类型,直接调用newInstance(),返回对象;

private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    if (mappedJdbcTypes != null) {
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
        register(javaType, handledJdbcType, typeHandler);
      }
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else {
      register(javaType, null, typeHandler);
    }
  }
方法的重载,第2行获取类上面的注解MappedJdbcTyoes,获取MappedJdbcTyoes注解中有定义JdbcType的属性;如果有jdbcType则用当前的jdbcTyoe;否则给null作为参数;

接下来看第5行代码:

private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
    if (javaType != null) {
      Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
      if (map == null) {
        map = new HashMap<JdbcType, TypeHandler<?>>();
        TYPE_HANDLER_MAP.put(javaType, map);
      }
      map.put(jdbcType, handler);
    }
    ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
  }

如果当前指定的typeHandler存在,则在当前的存储map中取获取;如果当前没有存储,则创建一个存储map;将指定的jdbctype作为key,存储typeHandler;

TYPE_HANDLER_MAP添加当前的 key:javaType value : map

用一个新增加的ALL_TYPE_HANDLERS_MAP存储新增加的typeHandler;

接下来我们看不使用注解的方式:

public <T> void register(TypeHandler<T> typeHandler) {
    boolean mappedTypeFound = false;
    MappedTypes mappedTypes = typeHandler.getClass().getAnnotation(MappedTypes.class);
    if (mappedTypes != null) {
      for (Class<?> handledType : mappedTypes.value()) {
        register(handledType, typeHandler);
        mappedTypeFound = true;
      }
    }
    // @since 3.1.0 - try to auto-discover the mapped type
    if (!mappedTypeFound && typeHandler instanceof TypeReference) {
      try {
        TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
        register(typeReference.getRawType(), typeHandler);
        mappedTypeFound = true;
      } catch (Throwable t) {
        // maybe users define the TypeReference with a different type and are not assignable, so just ignore it
      }
    }
    if (!mappedTypeFound) {
      register((Class<T>) null, typeHandler);
    }
  }
从1~9行代码还是进行对MappedTypes注解进行判断是否存在,上面已经讲述了;

接下来我们看到第11~18行获取到泛型指定的类进行给到方法入参,接下来就是跟上面方法一样了;

接下来我们看使用(javaType,jdbcType,handler)的方式:

          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
我们看到第4行代码:
protected Class<?> resolveClass(String alias) {
    if (alias == null) {
      return null;
    }
    try {
      return resolveAlias(alias);
    } catch (Exception e) {
      throw new BuilderException("Error resolving class. Cause: " + e, e);
    }
  }
根据配置的javaType值,如果为空则直接返回null;

继续往下面看到resolveAlias方法:

public <T> Class<T> resolveAlias(String string) {
    try {
      if (string == null) {
        return null;
      }
      // issue #748
      String key = string.toLowerCase(Locale.ENGLISH);
      Class<T> value;
      if (TYPE_ALIASES.containsKey(key)) {
        value = (Class<T>) TYPE_ALIASES.get(key);
      } else {
        value = (Class<T>) Resources.classForName(string);
      }
      return value;
    } catch (ClassNotFoundException e) {
      throw new TypeException("Could not resolve type alias '" + string + "'.  Cause: " + e, e);
    }
  }

第七行将字符串首字母小写转换

第九行判断当前类名解析器中有没有这个对象,如果有则直接返回;如果没有则根据配置的对象路径返回对象,路径错误则异常;

JdbcType jdbcType = resolveJdbcType(jdbcTypeName);从JdbcType返回jdbcType对象;

handlerTypeName获取和javaTypeName一样,就不多说了;

解析完三个参数;接着就根据参数分别注册不同的typeHandler;


接下来我们学习下自定义TypeHandler处理枚举;

在Mybatis中,处理枚举类的TypeHandler有两个:

  1. EnumTypeHandler: 用于保存枚举名
  2. EnumOrdinalTypeHandler: 用于保存枚举的序号。

在实际项目中,以上往往不能满足我们的需求。

需求分析

枚举需要包含两个属性,name(用于显示), id(实际的枚举值)。数据库保存枚举值(id)。

这很明显Mybatis提供的两个枚举TypeHandler不能满足我们的需求。此时,我们可以自定义一个通用的枚举TypeHandler来满足我们的需求。

自定义枚举TypeHandler

通用枚举DisplayedEnum 
/**
 * Copyright (C), 2015-2018, XXX有限公司
 * FileName: DisplayedEnum
 * Author:   temp
 * Date:     2018/3/29 16:05
 * Description:
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package TypeHandler;

import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;

/**
 * 〈一句话功能简述〉<br> 
 * 〈〉
 *
 * @author temp
 * @create 2018/3/29
 * @since 1.0.0
 */
public interface  DisplayedEnum {
    String DEFAULT_VALUE_NAME = "id";

    String DEFAULT_LABEL_NAME = "name";

    default Integer getValue() {
        Field field = ReflectionUtils.findField(this.getClass(), DEFAULT_VALUE_NAME);
        if (field == null)
            return null;
        try {
            field.setAccessible(true);
            return Integer.parseInt(field.get(this).toString());
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }


    default String getLabel() {
        Field field = ReflectionUtils.findField(this.getClass(), DEFAULT_LABEL_NAME);
        if (field == null)
            return null;
        try {
            field.setAccessible(true);
            return field.get(this).toString();
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    static <T extends Enum<T>> T valueOfEnum(Class<T> enumClass, Integer value) {
        if (value == null)
            throw  new IllegalArgumentException("DisplayedEnum value should not be null");
        if (enumClass.isAssignableFrom(DisplayedEnum.class))
            throw new IllegalArgumentException("illegal DisplayedEnum type");
        T[] enums = enumClass.getEnumConstants();
        for (T t: enums) {
            DisplayedEnum displayedEnum = (DisplayedEnum)t;
            if (displayedEnum.getValue().equals(value))
                return (T) displayedEnum;
        }
        throw new IllegalArgumentException("cannot parse integer: " + value + " to " + enumClass.getName());
    }

}

修改之前的普通枚举类
/**
 * Copyright (C), 2015-2018, XXX有限公司
 * FileName: web
 * Author:   temp
 * Date:     2018/3/29 11:03
 * Description:
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package enums;

import TypeHandler.DisplayedEnum;
import org.apache.ibatis.type.MappedTypes;

/**
 * 〈一句话功能简述〉<br> 
 * 〈〉
 *
 * @author temp
 * @create 2018/3/29
 * @since 1.0.0
 */
public enum  web implements DisplayedEnum {

    SING(1,"新浪"),
    QQ(2,"QQ"),
    SOHU(3,"搜狐"),
    FIREFOX(4,"火狐");

    private int id;

    private String name;

    private web(int id,String name){

        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }


}

说明:
普通枚举类通过实现DisplayedEnum接口,就可以:
  1. 通过getId()获取枚举值。
  2. 通过getNmae()获取枚举的name属性。
  3. 通过valueOfEnum()将Integer值转换为指定的枚举类型。
以上就是一个普通枚举类的示例。
自定义枚举TypeHandler
/**
 * Copyright (C), 2015-2018, XXX有限公司
 * FileName: DefaultEnumTypeHandler
 * Author:   temp
 * Date:     2018/3/29 16:48
 * Description:
 * History:
 * <author>          <time>          <version>          <desc>
 * 作者姓名           修改时间           版本号              描述
 */
package Handler;

import TypeHandler.DisplayedEnum;
import enums.web;
import model.AssembledMail;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 〈一句话功能简述〉<br> 
 * 〈〉
 *
 * @author temp
 * @create 2018/3/29
 * @since 1.0.0
 */
@MappedJdbcTypes(value = JdbcType.TINYINT, includeNullJdbcType = true)
@MappedTypes(value = {web.class})
public class DefaultEnumTypeHandler extends BaseTypeHandler<DisplayedEnum> {

    private Class<DisplayedEnum> type;

    public DefaultEnumTypeHandler(){
        System.out.print(111);
    };

    public DefaultEnumTypeHandler(Class<DisplayedEnum> type) {
        if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
        this.type = type;
    }

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, DisplayedEnum displayedEnum, JdbcType jdbcType) throws SQLException {
        preparedStatement.setInt(i, displayedEnum.getValue());
    }

    @Override
    public DisplayedEnum getNullableResult(ResultSet resultSet, String s) throws SQLException {
        return convert(resultSet.getInt(s));
    }

    @Override
    public DisplayedEnum getNullableResult(ResultSet resultSet, int i) throws SQLException {
        return convert(resultSet.getInt(i));
    }

    @Override
    public DisplayedEnum getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        return convert(callableStatement.getInt(i));
    }

    private DisplayedEnum convert(int status){
        DisplayedEnum[] objs = type.getEnumConstants();
        for(DisplayedEnum em: objs){
            if(em.getValue() == status){
                return  em;
            }
        }
        return null;
    }
}
使用我们自定义的DefaultEnumTypeHandler

注意:不能同时和包扫描使用

<typeHandlers>
         <!--javaType 配置java类型,例如String, 如果配上javaType, 那么指定的typeHandler就只作用于指定的类型 -->
        <typeHandler handler="Handler.AddressTypeHandler"/>
        <!--<typeHandler handler="org.apache.ibatis.type.EnumTypeHandler"  /> 使用枚举名称作为参数传递-->
        <typeHandler handler="Handler.DefaultEnumTypeHandler" /><!-- 使用整数下标作为参数传递-->
    </typeHandlers>

由于Mybatis默认在处理枚举类型的时候会使用EnumTypeHandler(只保存及转换枚举类型的名字), 因此,我们需要手动指定使用DefaultEnumTypeHandler。示例如下:

<resultMap type="Mail" id="MailResultMap">
         <result column="id" property="id" />
         <result column="create_time" property="createTime" />
         <result column="modify_time" property="modifyTime" />
         <result column="web_id" property="webId"  typeHandler="Handler.DefaultEnumTypeHandler" />
         <result column="mail" property="mail"   typeHandler="Handler.AddressTypeHandler"/>
         <result column="use_for" property="useFor" />
     </resultMap>
测试结果正确
即我们需要通过使用typeHandler来指定。
以上是我们应用在实际项目中的一个对于Mybatis处理枚举类的方案。我看大多数人也都是这样在用。

























  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值