IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Mybatis类型处理器 -> 正文阅读

[Java知识库]Mybatis类型处理器

一、TypeHandler

Mybatis版本3.5.4

在项目开发中经常会遇到一个问题

当我们在javabean中自定义了枚举类型或者其它某个类型,但是在数据库中存储时往往需要转换成数据库对应的类型,并且在从数据库中取出来时也需要将数据库类型转换为javabean中的对应类型。比如:javabean中字段类型为Date,数据库中存储的是varchar类型;javabean中字段类型是Enum,数据库中存储的是String或者Integer。

因为有大量类似数据的转换,手动转换类型进行存储和查询已经过于麻烦。MyBatis为我们提供了解决办法:TypeHandler类型处理器。

public interface TypeHandler<T> {
  
   将参数从java数据类型转化成jdbc数据类型,然后绑定到sql上
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  
  //从数据库中读取的列数据转换成java数据类型
  T getResult(ResultSet rs, String columnName) throws SQLException;
  T getResult(ResultSet rs, int columnIndex) throws SQLException;
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}

如果我想自定义我们自己的类型处理器,可以继续他的子类BaseTypeHandler,目前常用的类型处理器都继承于TypeHandler的子类BaseTypeHandler

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  @Deprecated
  protected Configuration configuration;

  @Deprecated
  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }


  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
  
  @Override
  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      if (jdbcType == null) {
        throw new TypeException("");
      }
      try {
        //处理把null映射成数据库类型的情况
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException(" ");
      }
    } else {
      try {
        //穿参不为空时,怎么把null映射成数据库类型
        setNonNullParameter(ps, i, parameter, jdbcType);
      } catch (Exception e) {
        throw new TypeException("" );
      }
    }
  }

  @Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    try {
      return getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException(" " + e, e);
    }
  }

  @Override
  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    try {
      return getNullableResult(rs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException(" " + e, e);
    }
  }

  @Override
  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    try {
      return getNullableResult(cs, columnIndex);
    } catch (Exception e) {
      throw new ResultMapException(" " + e, e);
    }
  }
}

二、类型处理器注册源码分析

在纯Mybatis环境中,Mybatis通从这段代码开始获取我们注册的类型处理器信息

private void typeHandlerElement(XNode parent) {
    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);
          }
        }
      }
    }
  }

目前常见类型处理器注册方式有2种

2.1 package注册方式源码分析
public final class TypeHandlerRegistry {

	   // 记录JdbcType和TypeHandler之间的对应关系,其中JdbcType是一个枚举类型,它定义对应的JDBC类型
	  // 该集合主要用于从结果集读取数据时,将数据从Jdbc类型转换成Java类型
	  private final Map<JdbcType, TypeHandler<?>>  jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
	  
	  // 记录了Java类型向指定的JdbcType转换时,需要使用的TypeHandler对象。例如:Java类型中的String可能
	  // 转换成数据库的char、varchar等多种类型,所以存在一对多的关系,所以值要用Map来存储
	  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
	  
	  // 未知类型对象的TypeHandler
	  private final TypeHandler<Object> unknownTypeHandler = new UnknownTypeHandler(this);
	  
	  // 记录了全部TypeHandler的类型以及该类型相应的TypeHandler对象
	  private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
	
	  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
	
	  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;


	 public void register(String packageName) {
	    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
	    //获取packageName包下类型是TypeHandler的类
	    resolverUtil.find(new ResolverUtil.IsA(TypeHandler.class), packageName);
	    Set<Class<? extends Class<?>>> handlerSet = resolverUtil.getClasses();
	    for (Class<?> type : handlerSet) {
	      //类不是匿名类   类不是接口     类不是抽象类
	      if (!type.isAnonymousClass() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) {
	        register(type);
	      }
	    }
	  }

	 public void register(Class<?> typeHandlerClass) {
	    boolean mappedTypeFound = false;
	    //类上是否标注了@MappedTypes注解,其用于指定当前类型处理器处理的java类
	    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
	    if (mappedTypes != null) {
	      for (Class<?> javaTypeClass : mappedTypes.value()) {
	        //将其注册到typeHandlerMap类型处理器池中,
	        //我们可以使用@MappedJdbcTypes注解,表明其该TypeHandler把java类型变成那种jdbctype
	        register(javaTypeClass, typeHandlerClass);
	        mappedTypeFound = true;
	      }
	    }
	    //
	    if (!mappedTypeFound) {
	       //1、判断其是否是TypeReference<T>类型,如果是,获取其T的类型,将其作为该TypeHandler的javaType
	       //如果不是TypeReference<T>类型,那么就不会把其注册到typeHandlerMap中,只会注册到allTypeHandlersMap
	      register(getInstance(null, typeHandlerClass));
	    }
	  }
}

结论:
1、当我们使用package方式注册TypeHandler时,如果我们的TypeHandler没有继承TypeReference<T>类,那么我们就要使用@MappedTypes注解,标准其用于指定处理的java类。否则会报错,如果我们想指定其jdbcType,我们可以使用@MappedJdbcTypes注解
2、当我们使用package方式注册TypeHandler时,如果我们的TypeHandler继承了TypeReference<T>类,那么其处理的java类就是T,如果我们想改变其指定处理的java类,我们可以使用@MappedTypes注解,如果我们想指定其jdbcType,我们可以使用@MappedJdbcTypes注解

案例

  <typeHandlers>
    <package name="com.clyu.config"/>
  </typeHandlers>

Mybatis中有2种枚举类型处理器是他们分别是EnumTypeHandler(用来存储枚举的名称)和EnumOrdinalTypeHandler(用来存储枚举的序数值),其默认枚举类型处理器是EnumTypeHandler

//如果MyEnumCodeTypeHandler只TypeHandler,那么,必须显示用@MappedTypes注解指定javaType
public class MyEnumCodeTypeHandler extends BaseTypeHandler<MyEnum> {
  
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, MyEnum parameter, JdbcType jdbcType) throws SQLException {
    if (jdbcType == null) {
      ps.setString(i, parameter.getCode());
    } else {
      ps.setObject(i, parameter.getCode(), jdbcType.TYPE_CODE); // see r3589
    }
  }

  @Override
  public MyEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
    String s = rs.getString(columnName);
    return MyEnum.getMyEnum(s);
  }

  @Override
  public MyEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    String s = rs.getString(columnIndex);
    return MyEnum.getMyEnum(s);
  }

  @Override
  public MyEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    String s = cs.getString(columnIndex);
    return MyEnum.getMyEnum(s);
  }
}
2.2 typeHandler注册方式源码分析

typeHandler注册方式有2中情况,一是javaType和handler为空,

<typeHandlers>
    <typeHandler handler="com.clyu.config.MyEnumCodeTypeHandler" javaType="com.clyu.config.MyEnum" jdbcType="int"/>
</typeHandlers>

其原理如下

public final class TypeHandlerRegistry {
  
  public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
    register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
  }
  
  public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
    register((Type) javaType, typeHandler);
  }
  
 private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
    MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
    //从这里可以看出@MappedJdbcTypes注解值会覆盖xml中的jdbcType
    if (mappedJdbcTypes != null) {
      for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
        register(javaType, handledJdbcType, typeHandler);
      }
      if (mappedJdbcTypes.includeNullJdbcType()) {
        register(javaType, null, typeHandler);
      }
    } else {
     //这里忽略了xml中的jdbcType
      register(javaType, null, typeHandler);
    }
  }
 }
<typeHandlers>
    <typeHandler handler="com.clyu.config.MyEnumCodeTypeHandler"  jdbcType="int"/>
</typeHandlers>

这种方式必须使用@MappedTypes注解指定javaType

其原理如下

public final class TypeHandlerRegistry {
  
 public void register(Class<?> typeHandlerClass) {
	    boolean mappedTypeFound = false;
	    //类上是否标注了@MappedTypes注解,其用于指定当前类型处理器处理的java类
	    MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
	    if (mappedTypes != null) {
	      for (Class<?> javaTypeClass : mappedTypes.value()) {
	        //将其注册到typeHandlerMap类型处理器池中,
	        //我们可以使用@MappedJdbcTypes注解,表明其该TypeHandler把java类型变成那种jdbctype
	        register(javaTypeClass, typeHandlerClass);
	        mappedTypeFound = true;
	      }
	    }
	    //
	    if (!mappedTypeFound) {
	       //1、判断其是否是TypeReference<T>类型,如果是,获取其T的类型,将其作为该TypeHandler的javaType
	       //如果不是TypeReference<T>类型,那么就不会把其注册到typeHandlerMap中,只会注册到allTypeHandlersMap
	      register(getInstance(null, typeHandlerClass));
	    }
}
<typeHandlers>
    <typeHandler handler="com.clyu.config.MyEnumCodeTypeHandler" javaType="com.clyu.config.MyEnum"/>
</typeHandlers>

其原理如下

public final class TypeHandlerRegistry {
  
   public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
   //getInstance方法是获取TypeHandler的对象
    register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
  }
  
  public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
    register((Type) javaType, typeHandler);
  }

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);
    }
  }

 }

三、默认注册的TypeHandler

public final class TypeHandlerRegistry {

  //这里指定了默认的枚举处理器EnumTypeHandler
  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;

  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 StringTypeHandler());
    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 StringTypeHandler());
    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, unknownTypeHandler);
    register(Object.class, JdbcType.OTHER, unknownTypeHandler);
    register(JdbcType.OTHER, unknownTypeHandler);

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

    register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());

    register(Instant.class, new InstantTypeHandler());
    register(LocalDateTime.class, new LocalDateTimeTypeHandler());
    register(LocalDate.class, new LocalDateTypeHandler());
    register(LocalTime.class, new LocalTimeTypeHandler());
    register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
    register(OffsetTime.class, new OffsetTimeTypeHandler());
    register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
    register(Month.class, new MonthTypeHandler());
    register(Year.class, new YearTypeHandler());
    register(YearMonth.class, new YearMonthTypeHandler());
    register(JapaneseDate.class, new JapaneseDateTypeHandler());

    // issue #273
    register(Character.class, new CharacterTypeHandler());
    register(char.class, new CharacterTypeHandler());
  }
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-17 11:46:11  更:2021-07-17 11:47:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/8 5:34:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码