MyBatis 类型转换模块

2023-09-21 11:14:18

前言

MyBatis是一个持久层框架ORM框架,实现数据库中数据和Java对象中的属性的双向映射,那么不可避免的就会碰到类型转换的问题,在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换为JDBC类型,而从结果集中获取数据时,则需要从JDBC类型转换为Java类型,所以本文来看下在MyBatis中是如何实现类型的转换的。

TypeHandler

MyBatis中的所有的类型转换器都继承了TypeHandler接口,在TypeHandler中定义了类型转换器的最基本的功能。

/**
 * @author Clinton Begin
 */
public interface TypeHandler<T> {

  /**
   * 负责将Java类型转换为JDBC的类型
   *    本质上执行的就是JDBC操作中的 如下操作
   *        String sql = "SELECT id,user_name,real_name,password,age,d_id from t_user where id = ? and user_name = ?";
   *        ps = conn.prepareStatement(sql);
   *        ps.setInt(1,2);
   *        ps.setString(2,"张三");
   * @param ps
   * @param i 对应占位符的 位置
   * @param parameter 占位符对应的值
   * @param jdbcType 对应的 jdbcType 类型
   * @throws SQLException
   */
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * 从ResultSet中获取数据时会调用此方法,会将数据由JdbcType转换为Java类型
   * @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
   */
  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的实现,在MyBatis中提供了BaseTypeHandler这个抽象类,它实现了TypeHandler接口,并继承了TypeReference类,

image.png

在BaseTypeHandler中的实现方法中实现了对null的处理,非空的处理是交给各个子类去实现的。这个在代码中很清楚的体现了出来

TypeHandler实现类

TypeHandler的实现类比较多,而且实现也都比较简单。

image.png

以Integer为例

/**
 * @author Clinton Begin
 */
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
      throws SQLException {
    ps.setInt(i, parameter); // 实现参数的绑定
  }

  @Override
  public Integer getNullableResult(ResultSet rs, String columnName)
      throws SQLException {
    int result = rs.getInt(columnName); // 获取指定列的值
    return result == 0 && rs.wasNull() ? null : result;
  }

  @Override
  public Integer getNullableResult(ResultSet rs, int columnIndex)
      throws SQLException {
    int result = rs.getInt(columnIndex); // 获取指定列的值
    return result == 0 && rs.wasNull() ? null : result;
  }

  @Override
  public Integer getNullableResult(CallableStatement cs, int columnIndex)
      throws SQLException {
    int result = cs.getInt(columnIndex); // 获取指定列的值
    return result == 0 && cs.wasNull() ? null : result;
  }
}

TypeHandlerRegistry

在MyBatis中给我们提供的具体的类型转换器实在是太多了,那么在实际的使用时我们是如何知道使用哪个转换器类处理的呢?实际上再MyBatis中是将所有的TypeHandler都保存注册在了TypeHandlerRegistry中的。首先注意声明的相关属性

  // 记录JdbcType和TypeHandle的对应关系
  private final Map<JdbcType, TypeHandler<?>>  jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
  // 记录Java类型向指定的JdbcType转换时需要使用到的TypeHandle
  private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();
  private final TypeHandler<Object> unknownTypeHandler;
  // 记录全部的TypeHandle类型及对应的TypeHandle对象
  private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();

  // 空TypeHandle的标识
  private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();

在器构造方法中完成了系统提供的TypeHandler的注册

image.png

有注册的方法,当然也有从注册器中获取TypeHandler的方法,getTypeHandler方法,这个方法也有多个重载的方法,这里重载的方法最终都会执行的方法是

  /**
   * 根据对应的Java类型和Jdbc类型来查找对应的TypeHandle
   */
  private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
    if (ParamMap.class.equals(type)) {
      return null;
    }
    // 根据Java类型获取对应的 Jdbc类型和TypeHandle的集合容器
    Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
    TypeHandler<?> handler = null;
    if (jdbcHandlerMap != null) {
      // 根据Jdbc类型获取对应的 处理器
      handler = jdbcHandlerMap.get(jdbcType);
      if (handler == null) {
        // 获取null对应的处理器
        handler = jdbcHandlerMap.get(null);
      }
      if (handler == null) {
        // #591
        handler = pickSoleHandler(jdbcHandlerMap);
      }
    }
    // type drives generics here
    return (TypeHandler<T>) handler;
  }

除了使用系统提供的TypeHandler以外,还可以创建自己的TypeHandler了,具体使用见MyBatis 核心配置

TypeAliasRegistry

在MyBatis的应用的时候会经常用到别名,这能大大简化我们的代码,其实在MyBatis中是通过TypeAliasRegistry类管理的。

image.png

注册的方法逻辑也比较简单

  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748 别名统一转换为小写
    String key = alias.toLowerCase(Locale.ENGLISH);
    // 检测别名是否存在
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    // 将 别名 和 类型 添加到 Map 集合中
    typeAliases.put(key, value);
  }

TypeHandler的应用

SqlSessionFactory

在构建SqlSessionFactory时,在Configuration对象实例化的时候在成员变量中完成了TypeHandlerRegistry和TypeAliasRegistry的实例化

image.png

在TypeHandlerRegistry的构造方法中完成了常用类型的TypeHandler的注册

image.png

在TypeAliasRegistry中完成了常用Java类型别名的注册

image.png

在Configuration的构造方法中会为各种常用的类型向TypeAliasRegistry中注册类型别名数据

image.png

以上步骤完成了TypeHandlerRegistry和TypeAliasRegistry的初始化操作

然后在解析全局配置文件时会通过解析<typeAliases>标签和<typeHandlers>标签,可以注册我们添加的别名和TypeHandler。

image.png

在全局配置文件中指定了对应的别名,那么在映射文件中就可以简写类型了,这样在解析映射文件时,我们同样也是需要做别名的处理的。

image.png

这个parameterType就可以是我们定义的别名,然后在 resolveClass中就会做对应的处理

  protected <T> Class<? extends T> resolveClass(String alias) {
    if (alias == null) {
      return null;
    }
    try {
      return resolveAlias(alias); // 别名处理
    } catch (Exception e) {
      throw new BuilderException("Error resolving class. Cause: " + e, e);
    }
  }

  protected <T> Class<? extends T> resolveAlias(String alias) {
    return typeAliasRegistry.resolveAlias(alias); // 根据别名查找真实的类型
  }

执行SQL语句

TypeHandler类型处理器使用比较多的地方应该是在给SQL语句中参数绑定值和查询结果和对象中属性映射的地方用到的比较多,

DefaultParameterHandler中看看参数是如何处理的

  /**
  * 为 SQL 语句中的 ? 占位符 绑定实参
  */
  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
      // 取出SQL中的参数映射列表
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); // 获取对应的占位符
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) { // 过滤掉存储过程中的 输出参数
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { // 
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // 获取 参数类型 对应的 类型处理器
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // 通过TypeHandler 处理参数
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

然后进入到DefaultResultSetHandler中的getRowValue方法中

image.png

然后再进入applyAutomaticMappings方法中查看

image.png

可以看到,根据对应的TypeHandler返回对应类型的值。

更多推荐

LabVIEW在运行时调整表控件列宽

LabVIEW在运行时调整表控件列宽如何在LabIEW中运行时调整表控件的列宽大小?在VI运行时,有两种不同的方法可以更改表中列的宽度。首先,可以使用鼠标手动更改它们;其次,可以从框图中以编程方式更改它们。手动更改列宽只有在启用列标题时,才能使用鼠标更改表的列宽。默认情况下,在LabVIEW中,列标题是禁用的。请按照以

观测云接入 NewRelic .NET 探针

背景部分客户系统采用的是.NET4.5部署研发的、基于IIS进行发布的Web项目,需要接入到观测云进行链路信息展示,ddtrace和otel对于低版本.NET支持力度有限。环境信息IIS4.0.NETcore4.0、4.5、4.6WindowServer2012R2域名配置准备一个域名www.datakit.com,需

ubuntu+.net6+docker 应用部署教程

先期工作1、本地首先安装DockerDesktop2、本地装linuxinwindows3、生成镜像后期工作1、云服务器部署生成镜像方法1、生成Dockerfile配置文件开发工具visualstudio2022如果项目已经存在,可以选中项目,右键点击->选择添加Docker支持。继续选linux项目支持docker后

基于深度学习网络的烟雾检测算法matlab仿真

目录1.算法运行效果图预览2.算法运行软件版本3.部分核心程序4.算法理论概述5.算法完整程序工程1.算法运行效果图预览2.算法运行软件版本matlab2022a3.部分核心程序.......................................................................

Tomcat常见报错以及手动实现Tomcat

一.Tomcat的简单启动1.安装Tomcat2.Tomcat启动1.双击bin目录下的startup.bat文件2.输入http://localhost:8080/,显示如下界面代表安装成功,默认在8080端口3.注意,不要关闭黑窗口,关闭了,tomcat服务就停止了.3.曾经的启动失败案例3.1Tomcat停止时报

Postman —— post请求数据类型

1、Postman中post的数据类型post中有以下数据类型1、form-data2、x-www-form-urlencoded3、raw4、binary2、Postman请求不同的post数据类型from-datamultipart/form-data,它将表单的数据组织成Key-Value形式,也可以上传文件,当

mybatis/mp批量插入非自增主键数据

文章目录前言一、mp的批量插入是假的二、真正的批量插入1.利用sql注入器处理2.采用自编码,编写xml批量执行生成内容如下:三问题问题描述问题原因问题解决粘贴一份,兼容集合替换原有文件总结自增与非自增区别:前言mybatis/mp在实际开发中是常用的优秀持久层框架,但是在非自增主键的时候,单条数据插入式可以的,当批量

[pai-diffusion]pai的easynlp的clip模型训练

EasyNLP带你玩转CLIP图文检索-知乎作者:熊兮、章捷、岑鸣、临在导读随着自媒体的不断发展,多种模态数据例如图像、文本、语音、视频等不断增长,创造了互联网上丰富多彩的世界。为了准确建模用户的多模态内容,跨模态检索是跨模态理解的重要任务,…https://zhuanlan.zhihu.com/p/528476134

大型语言模型:SBERT — 句子BERT

了解siameseBERT网络如何准确地将句子转换为嵌入简介Transformer在NLP领域取得了进化性的进步,这已不是什么秘密。基于Transformer,还发展出了许多其他机器学习模型。其中之一是BERT,它主要由几个堆叠的Transformer编码器组成。除了用于一系列不同的问题(例如情感分析或问答)之外,BE

云原生Kubernetes:K8S集群list-watch机制与 pod调度约束

目录一、理论1.K8S的list-watch机制2.亲和性二、实验1.指定调度节点2.节点亲和性3.亲和性和反亲和三、问题1.新生成pod一直为pending2.如何一次性删除pod和deployment3.pod亲和性资源报错4.pod反亲和性资源报错四、总结一、理论1.K8S的list-watch机制(1)概念Ku

前端防抖和节流

前端防抖和节流概述防抖:防止抖动,个人字面理解此处防的不是页面的抖动,而是用户手抖。为了防止用户快速且频繁的触发事件而导致多次执行事件函数,这样的场景有很多,比如监听滚动、鼠标移动事件onmousemove、频繁点击表单的提交按钮等等。节流:节约流量,为了节约流量,页面在一个时间周期内,只触发一次动作。所以节流的目的时

热文推荐