Mybatis TypeHandler接口及继承关系示例解析

目录
  • 开篇
  • TypeHandler接口
    • TypeHandler继承体系
      • IntegerTypeHandler
      • DateTypeHandler
  • TypeHandlerRegistry
    • TypeHandlerRegistry#register方法
  • 总结

开篇

JDBC类型与Java类型并不是完全一一对应的。所以在PreparedStatement绑定参数的时候需要把Java类型转为JDBC类型。JDBC类型的枚举值在JdbcType枚举值中存储。

MyBatis中提供了一个接口专用于JDBC类型与Java类型的转换。它就是我们今天的主题:TypeHandler(类型转换器)

TypeHandler接口

TypeHandler是用于JDBC类型与Java类型的转换

我们先来看一下这个接口的定义,它都规范了哪些行为。再来说这些方法的实现类和具体作用。

public interface TypeHandler<T> {
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  T getResult(ResultSet rs, String columnName) throws SQLException;
  T getResult(ResultSet rs, int columnIndex) throws SQLException;
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}

该接口中共有4个方法。这四个方法只定义了两种行为,一是setParameter(设置参数)二是操作结果集获取指定对象

方法名 描述
setParameter 使用PreparedStatement执行SQL是,设置带?的参数。
getResult 获取结果集中指定列名对应的值,或者获取结果集中指定索引对应的值

实际setParameter方法的底层实现就是JDBC操作

PreparedStatement ps = connection.prepareStatement();
ps.setString(1,"value");

getResult方法的底层同样也是JDBC操作

// rs是结果集对象ResultSet
rs.getInt(colName);
rs.getInt(i)

TypeHandler继承体系

在MyBatis中JDBC类型被定义在枚举类JdbcType中。共有41中JDBC类型。每个JDBC类型都对应一个XxxTypeHandler类。每个类都是解决特定的JDBC类型转换

TypeHandler接口值规定了行为,每种JDBC类型,都提供了对应的实现类来完场JDBC到Java类型的转换。

每个TypeHandler的实现都大同小异,而为了避免空指针的问题,TypeHandler还有一个抽象子类,对这4个方法做了模板处理。

  • 设置参数:如果值为null则直接跳过,否则调用具体实现类设置参数
  • 从结果集中获取数据:调用具体实现类的getNullableResult方法

BaseTypeHandler的代码比较简单易懂,这列举重要实现:

public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
  if (parameter == null) {
    if (jdbcType == null) {抛异常}
    try {
      ps.setNull(i, jdbcType.TYPE_CODE);
    } catch (SQLException e) { 抛异常 }
  } else {
    try {
      // 如果参数不为Null才调用子类方法
      setNonNullParameter(ps, i, parameter, jdbcType);
    } catch (Exception e) { 抛异常 }
  }
}

我们先来剖析下常用的类型。IntegerTypeHandler

IntegerTypeHandler

public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType) throws SQLException {
    ps.setInt(i, parameter);
  }
  public Integer getNullableResult(ResultSet rs, String columnName) throws SQLException {
    int result = rs.getInt(columnName);
    return result == 0 && rs.wasNull() ? null : result;
  }
  // 省略另外两个方法
}

可以看到IntegerTypeHandler的底层就是调用了JDBC对象PreparedStatement的方法来设置参数与获取结果集中的记录。这个实现是最简单的一个,就不展开介绍了。

下面我们再来看一下日期类型的映射

DateTypeHandler

public class DateTypeHandler extends BaseTypeHandler<Date> {
  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType) throws SQLException {
    ps.setTimestamp(i, new Timestamp(parameter.getTime()));
  }
  @Override
  public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
    Timestamp sqlTimestamp = rs.getTimestamp(columnName);
    if (sqlTimestamp != null) {
      return new Date(sqlTimestamp.getTime());
    }
    return null;
  }
}
  • 设置参数:我们使用Java编程,使用的日期常用的类是java.util.Date,但是在JDBC操作数据库的时候,往往需要的不是Date类型,而是SQL标准定义的Timestamp类型,DateTypeHandler会帮助我们把Date转化为Timestamp
  • 获取结果,从JDBC中获取的记录如果是Timestamp类型,则DateTypeHandler帮我们转为java.util.Date对象

注:可能大家在编程JDBC的时候,执行如下语句select * from user where birthday > ?,无论数据库中birthday字段是Date/DateTime/TimeStamp类型,我们都会把会把?的值设置为Date类型,实际上这是不符合JDBC标准的。

其他TypeHandler的实现,就不再看了。逻辑都是一样的。

TypeHandlerRegistry

通过上一小节了解到了每一种JDBC类型,都提供了TypeHandler的实现。那么mybatis什么时候才会用到这些实现类呢?或者说当需要进行类型转换的时候,MyBatis是如何使用这些实现类的。

在MyBatis初始化的时候,会加载TypeHandlerRegistry这个类,这个类有在构造方法里会调用一系列的register方法把TypeHandler所有实现类都注册到TypeHandlerRegistry中。下面我们先来看一下TypeHandlerRegistry中的重要属性

public final class TypeHandlerRegistry {
  private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
  private final TypeHandler<Object> unknownTypeHandler;
  private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
}
  • jdbcTypeHandlerMap:key是Jdbc类型,value是Jdbc类型对应的处理器
  • typeHandlerMap:key是Java类型,value是对应的处理器集合(它也是要给Map)。因为一个Java类型可能被多个处理器解析。比如String类型可能被解析为char varchar类型等。比如Java的java.util.Date类型可以被解析为Date Time TimeStamp类型。存在一对多的关系
  • unknownTypeHandler:位置的TypeHandler,一般为空
  • allTypeHandlersMap:所有类型处理器,key是TypeHandler实现类的Class对象,value是具体的TypeHandler

TypeHandlerRegistry#register方法

上面介绍了TypeHandlerRegistry中几个重要的属性。在mybatis启动时,就会把所有的TypeHandler都注册到如上的4个数据结构中。具体的实现在TypeHandlerRegistry的构造方法中,调用register方法进行注册TypeHandler

public TypeHandlerRegistry(Configuration configuration) {
  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
}

这些register最终将如上的3个Map数据结构填充。通过名字也能看出来,注册Class/JdbcType与TypeHandler的映射关系。而这些register分为两大类,一类是注册JDBC-TypeHandler映射关系,一类是注册Java-TypeHandler的映射关系。我们先来看第一种

public void register(JdbcType jdbcType, TypeHandler<?> handler) {
  jdbcTypeHandlerMap.put(jdbcType, handler);
}

它的实现很简单,就是直接把jdbcType作为key,TypeHandler作为value,存入jdbcTypeHandlerMap即可。接下来我们再来看第二种:注册Java-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);
  }
}

我们来分析下 注册步骤:

  • 获取MappedJdbcTypes注解,对自定义的TypeHandler进行注册。(自定义TypeHandler时需要该注解指定自定义的TypeHandler都解析哪些JDBC类型)
  • 如果TypeHandler没有注解信息(也就是没有自定义TypeHandler)则直接调用重载register方法注册Java-TypeHandler的映射关系。

下面就来看一下这个重载register方法

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

代码中它主要做了两件事:

  • 注册Java-TypeHandler的映射关系
  • 把TypeHandler的Class和类的映射关系存入allTypeHandlersMap

至此TypeHandlerRegistry中就有了最重要的两种映射关系

  • JDBC类型 与 TypeHandler 的映射关系(通过Jdbc类型寻找合适的TypeHandler类型,通常用于结果集的解析过程)
  • Java类型 与 TypeHandler 的映射关系(通过Java类型寻找合适的TypeHandler类型,通常用于为PreparedStatement对象设置参数)

TypeHandlerRegistry也提供了一系列的get*方法,可以根据指定的信息返回需要的TypeHandler类

  • getMappingTypeHandler(Class>):根据TypeHandler类型获取对应的TypeHandler对象
  • getTypeHandler(JdbcType):根据JdbcType类型获取TypeHandler对象
  • getTypeHandler(Class):根据Class类型获取TypeHandler对象
  • getJdbcHandlerMap(Type):根据Java类型获取TypeHandler对象

总结

TypeHandler是类型处理器,它用来解析Java类型与Jdbc类型之间的相互转换。

TypeHandlerRegistry是一个注册器,其中注册了JDBC与TypeHandler的映射关系、Java类型与TypeHandler的映射关系。

那么由此我们可以想象到,在mybatis执行SQL的过程中,一定会在某处调用TypeHandlerRegistry并通过参数的Java类型获取对应的TypeHandler对象为PreparedStatement设置参数。

也一定会在解析结果集的过程中,调用TypeHandlerRegistry并通过结果集数据的Jdbc类型获取对应的TypeHandler对象为来解析结果集中的记录

以上就是Mybatis TypeHandler接口及继承关系示例解析的详细内容,更多关于Mybatis TypeHandler接口继承的资料请关注我们其它相关文章!

(0)

相关推荐

  • MyBatis的MapKey注解实例解析

    目录 使用 一.数据准备 二.Mapper配置 UserMapper接口 三.实战 实战2——注意事项 原理 总结 使用 mybatis中有很多实用的注解,但是平时想不起来使用.今天就来讲一下MapKey是如何使用的 说明:本文基于mybatis原生框架3.3.0-SNAPSHOT 一.数据准备 数据库准备一张user表,插入一点测试数据 CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(

  • MyBatis SqlSource源码示例解析

    目录 正文 SqlNode SqlNode接口定义 BoundSql SqlSource SqlSource解析时机 SqlSource调用时机 总结 正文 MyBatis版本:3.5.12. 本篇讲从mybatis的角度分析SqlSource.在xml中sql可能是带?的预处理语句,也可能是带$或者动态标签的动态语句,也可能是这两者的混合语句. SqlSource设计的目标就是封装xml的crud节点,使得mybatis运行过程中可以直接通过SqlSource获取xml节点中解析后的SQL.

  • 更简单更高效的Mybatis Plus最新代码生成器AutoGenerator

    目录 正文 一.概述 二.使用AutoGenerator 1. 初始化数据库表结构(以User用户表为例) 2. 在 pom.xml 文件中添加 AutoGenerator 的依赖. 3. 添加模板引擎依赖 4. 全局配置 5. 自定义模板生成DTO.VO User用户类 总结 正文 MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发.提高效率而生. 今天的主角是MP推出的一款代码生成器,本文主要来介绍一下它强大的代

  • 详解MyBatis ResultSetHandler 结果集的解析过程

    目录 正文 ResultSetHandler#handleResultSets 第一部分:ResultSetWrapper 第二部分:验证rsw对象 第三部分:遍历rsw中的结果集 第四部分:处理ResultSets标签 第五部分:collapseSingleResultList 总结 正文 mybatis版本:3.5.12 mybatis通过Executor查询出结果后,通常返回的是一个List结构,再根据用户调用的API把List结构转为指定结构. 比如用户调用SqlSession#sele

  • Mybatis TypeHandler接口及继承关系示例解析

    目录 开篇 TypeHandler接口 TypeHandler继承体系 IntegerTypeHandler DateTypeHandler TypeHandlerRegistry TypeHandlerRegistry#register方法 总结 开篇 JDBC类型与Java类型并不是完全一一对应的.所以在PreparedStatement绑定参数的时候需要把Java类型转为JDBC类型.JDBC类型的枚举值在JdbcType枚举值中存储. MyBatis中提供了一个接口专用于JDBC类型与J

  • Mybatis mapper接口动态代理开发步骤解析

    一.必须遵守的四项原则 1:接口 方法名==xx.xml中的id名 2:方法返回值类型与Mapper.xml文件中返回值类型一致 3:方法的入参类型与Mapper.xml文件中入参值类型一致 4:命名空间绑定接口 二.代码 public class UserMapperTest { private SqlSession sqlSession; private InputStream in; @Before public void before() throws IOException { //1

  • PHP接口继承及接口多继承原理与实现方法详解

    本文实例讲述了PHP接口继承及接口多继承原理与实现方法.分享给大家供大家参考,具体如下: 在PHP的接口中,接口可以继承接口.虽然PHP类只能继承一个父类(单继承),但是接口和类不同,接口可以实现多继承,可以继承一个或者多个接口.当然接口的继承也是使用extends关键字,要多个继承的话只要用逗号把继承的接口隔开即可. 需要注意的是当你接口继承其它接口时候,直接继承父接口的静态常量属性和抽象方法,所以类实现接口时必须实现所有相关的抽象方法. 现在你对PHP接口的继承有所了解了吧,下面的例子可供参

  • java集合继承关系图分享

    面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式. 数组虽然也可以存储对象,但长度是固定的:集合长度是可变的,数组中可以存储基本数据类型,集合只能存储对象. 集合类的特点:集合只用于存储对象,集合长度是可变的,集合可以存储不同类型的对象. 上述类图中,实线边框的是实现类,比如ArrayList,LinkedList,HashMap等,折线边框的是抽象类,比如AbstractCollection,AbstractList,A

  • go语言interface接口继承多态示例及定义解析

    目录 1.什么是接口 2.接口定义 3.多态 多态加减计算器 4.接口继承与转换 5.空接口 6.接口转换 7.实现map字典接口 8.interface案例 1.什么是接口 接口就是一种规范与标准,在生活中经常见接口,例如:笔记本电脑的USB接口,可以将任何厂商生产的鼠标与键盘,与电脑进行链接.为什么呢?原因就是,USB接口将规范和标准制定好后,各个生产厂商可以按照该标准生产鼠标和键盘就可以了. 在程序开发中,接口只是规定了要做哪些事情,干什么.具体怎么做,接口是不管的.这和生活中接口的案例也

  • go语言数组及结构体继承和初始化示例解析

    目录 分类 数组 数组定义 结构体 结构体继承 结构体初始化 成员的操作 同名字段 其它匿名字段 非结构体类型 结构体指针类型 结构体字段实现接口 分类 类型 名称 长度 默认值 说明 pointer 指针   nil   array 数组   0   slice 切片   nil 引⽤类型 map 字典   nil 引⽤类型 struct 结构体       数组 如果要存储班级里所有学生的数学成绩,应该怎样存储呢?可能有同学说,通过定义变量来存储.但是,问题是班级有80个学生,那么要定义80

  • Mybatis Mapper接口工作原理实例解析

    KeyWords: Mybatis 原理,源码,Mybatis Mapper 接口实现类,代理模式,动态代理,Java动态代理, Proxy.newProxyInstance,Mapper 映射,Mapper 实现 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.我们在使用 Mybaits 进行 ,通常只需要定义几个 Mapper 接口,然后在编写一个 xml 文件,我们在配置文件中

  • php模拟post提交请求调用接口示例解析

    php模拟post提交请求,调用接口 /** * 模拟post进行url请求 * @param string $url * @param string $param */ function request_post($url = '', $param = '') { if (empty($url) || empty($param)) { return false; } $postUrl = $url; $curlPost = $param; $ch = curl_init();//初始化curl

  • 示例解析java设计模式七大原则接口隔离原则及优化

    目录 1.什么是接口隔离原则? 2.对应代码 3.改进代码 4.接口隔离原则总结 1.什么是接口隔离原则? 客户端不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口范围上. 2.对应代码 上面这张图呢,就违反了接口隔离原则.它对应的代码如下: package com.szh.principle.segregation; /** * */ interface Interface1 { void operation1(); void operation2(); void oper

随机推荐