持续原创输出,点击上方蓝字关注我吧

目录

  • 前言
  • 环境配置
  • 什么是TypeHandler?

    • 如何自定义?
    • 如何将其添加到Mybatis中?
    • XML文件中如何指定TypeHandler?
  • 源码中如何执行TypeHandler?

    • 入参如何转换?
    • 结果如何转换?
    • 总结
  • 总结

前言

  • 相信大家用Mybatis这个框架至少一年以上了吧,有没有思考过这样一个问题:数据库有自己的数据类型,Java有自己的数据类型,那么Mybatis是如何把数据库中的类型和Java的数据类型对应的呢?

  • 本篇文章就来讲讲Mybatis中的黑匣子TypeHandler(类型处理器),说它是黑匣子一点都不为过,总是在默默的奉献着,但是不为人知。

环境配置

  • 本篇文章讲的一切内容都是基于Mybatis3.5SpringBoot-2.3.3.RELEASE

什么是TypeHandler?

  • 顾名思义,类型处理器,将入参和结果转换为所需要的类型,Mybatis中对于内置了许多类型处理器,实际开发中已经足够使用了,如下图:

  • 类型处理器这个接口其实很简单,总共四个方法,一个方法将入参的Java类型的数据转换为JDBC类型,三个方法将返回结果转换为Java类型。源码如下:

public interface TypeHandler<T> {
//设置参数,java类型转换为jdbc类型
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;
}

如何自定义并使用TypeHandler?

  • 实际应用开发中的难免会有一些需求要自定义一个TypeHandler,比如这样一个需求:前端传来的年龄是,,但是数据库定义的字段却是int类型(1男2女)。此时可以自定义一个年龄的类型处理器,进行转换。

如何自定义?

  • 自定义的方式有两种,一种是实现TypeHandler这个接口,另一个就是继承BaseTypeHandler这个便捷的抽象类。
  • 下面直接继承BaseTypeHandler这个抽象类,定义一个年龄的类型处理器,如下:
@MappedJdbcTypes(JdbcType.INTEGER)
@MappedTypes(String.class)
public class GenderTypeHandler extends BaseTypeHandler { //设置参数,这里将Java的String类型转换为JDBC的Integer类型
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, StringUtils.equals(parameter.toString(),"男")?1:2);
} //以下三个参数都是将查询的结果转换
@Override
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getInt(columnName)==1?"男":"女";
} @Override
public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getInt(columnIndex)==1?"男":"女";
} @Override
public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getInt(columnIndex)==1?"男":"女";
}
}
  • 这里涉及到两个注解,如下:

    • @MappedTypes:指定与其关联的 Java 类型列表。 如果在 javaType 属性中也同时指定,则注解上的配置将被忽略。
    • @MappedJdbcTypes:指定与其关联的 JDBC 类型列表。 如果在 jdbcType 属性中也同时指定,则注解上的配置将被忽略。

如何将其添加到Mybatis中?

  • Mybatis在与SpringBoot整合之后一切都变得很简单了,其实这里有两种配置方式,下面将会一一介绍。
  • 「第一种」:只需要在配置文件application.properties中添加一行配置即可,如下:
## 设置自定义的Typehandler所在的包,启动的时候会自动扫描配置到Mybatis中
mybatis.type-handlers-package=cn.cb.demo.typehandler
  • 「第二种」:其实任何框架与Springboot整合之后,只要配置文件中能够配置的,在配置类中都可以配置(「除非有特殊定制,否则不要轻易覆盖自动配置」)。如下:
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATOIN));
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
// 自动将数据库中的下划线转换为驼峰格式
configuration.setMapUnderscoreToCamelCase(true);
configuration.setDefaultFetchSize(100);
configuration.setDefaultStatementTimeout(30);
sqlSessionFactoryBean.setConfiguration(configuration);
//将typehandler注册到mybatis
GenderTypeHandler genderTypeHandler = new GenderTypeHandler();
TypeHandler[] typeHandlers=new TypeHandler[]{genderTypeHandler};
sqlSessionFactoryBean.setTypeHandlers(typeHandlers);
return sqlSessionFactoryBean.getObject();
}
  • 第二种方式的思想其实就是重写自动配置类MybatisAutoConfiguration中的方法。「注意:除非自己有特殊定制,否则不要轻易重写自动配置类中的方法」

XML文件中如何指定TypeHandler?

  • 上面的两个步骤分别是自定义和注入到Mybatis中,那么如何在XML文件中使用呢?
  • 使用其实很简单,分为两种,一种是更新,一种查询,下面将会一一介绍。
  • 「更新」:删除自不必说了,这里讲的是updateinsert两种,只需要在#{}中指定的属性typeHandler为自定义的全类名即可,代码如下:
<insert id="insertUser">
insert into user_info(user_id,his_id,name,gender,password,create_time)
values(#{userId,jdbcType=VARCHAR},#{hisId,jdbcType=VARCHAR},#{name,jdbcType=VARCHAR},
#{gender,jdbcType=INTEGER,typeHandler=cn.cb.demo.typehandler.GenderTypeHandler},#{password,jdbcType=VARCHAR},now())
</insert>
  • 「查询」:查询的时候类型处理会将JDBC类型的转化为Java类型,因此也是需要指定typeHandler,需要在resultMap中指定typeHandler这个属性,值为全类名,如下:
<resultMap id="userResultMap" type="cn.cb.demo.domain.UserInfo">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="his_id" property="hisId"/>
<!-- 指定typeHandler属性为全类名-->
<result column="gender" property="gender" typeHandler="cn.cb.demo.typehandler.GenderTypeHandler"/>
<result column="name" property="name"/>
<result column="password" property="password"/>
</resultMap> <select id="selectList" resultMap="userResultMap">
select * from user_info where status=1
and user_id in
<foreach collection="userIds" item="item" open="(" separator="," close=")" >
#{item}
</foreach>
</select>

源码中如何执行TypeHandler?

  • 既然会使用TypeHandler了,那么肯定要知道其中的执行原理了,在Mybatis中类型处理器是如何在JDBC类型和Java类型进行转换的,下面的将从源码角度详细介绍。

入参如何转换?

  • 这个肯定是发生在设置参数的过程中,详细的代码在PreparedStatementHandler中的parameterize()方法中,这个方法就是设置参数的方法。源码如下:
 @Override
public void parameterize(Statement statement) throws SQLException {
//实际调用的是DefaultParameterHandler
parameterHandler.setParameters((PreparedStatement) statement);
}
  • 实际执行的是DefaultParameterHandler中的setParameters方法,如下:
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//获取参数映射
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 jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//调用类型处理器中的方法设置参数,将Java类型转换为JDBC类型
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
  • 从上面的源码中可以知道这行代码typeHandler.setParameter(ps, i + 1, value, jdbcType);就是调用类型处理器中的设置参数的方法,将Java类型转换为JDBC类型。

结果如何转换?

  • 这一过程肯定是发生在执行查询语句的过程中,之前也是介绍过Mybatis的六大剑客,其中的ResultSetHandler这个组件就是对查询的结果进行处理的,那么肯定是发生在这一组件中的某个方法。
  • PreparedStatementHandler执行查询结束之后,调用的是ResultSetHandler中的handleResultSets()方法,对结果进行处理,如下:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//执行SQL
ps.execute();
//处理结果
return resultSetHandler.handleResultSets(ps);
}
  • 最终的在DefaultResultHandler中的getPropertyMappingValue()方法中调用了TypeHandler中的getResult()方法,如下:
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
//执行typeHandler中的方法获取结果并且转换为对应的Java类型
return typeHandler.getResult(rs, column);
}
}

总结

总结

  • 本文详细的介绍了TypeHandler在Mybatis中的应用、自定义使用以及从源码角度分析了类型处理器的执行流程,如果觉得作者写的不错,有所收获的话,不妨点点关注,分享一波。

面试官问:Mybatis中的TypeHandler你用过吗?的更多相关文章

  1. 【Java8新特性】面试官问我:Java8中创建Stream流有哪几种方式?

    写在前面 先说点题外话:不少读者工作几年后,仍然在使用Java7之前版本的方法,对于Java8版本的新特性,甚至是Java7的新特性几乎没有接触过.真心想对这些读者说:你真的需要了解下Java8甚至以 ...

  2. 面试官问我,Redis分布式锁如何续期?懵了。

    前言 上一篇[面试官问我,使用Dubbo有没有遇到一些坑?我笑了.]之后,又有一位粉丝和我说在面试过程中被虐了.鉴于这位粉丝是之前肥朝的粉丝,而且周一又要开启新一轮的面试,为了回馈他长期以来的支持,所 ...

  3. 面试官问,说一个你在工作非常有价值的bug

    如果你去参考面试,做足了准备,面对面试官员从容不迫,吐沫横飞的大谈自己的工作经历.突然,面试官横插一句:说一个你在工作非常有价值的bug.顿时,整个空气都仿佛都凝固了!“What?”... 我想没几个 ...

  4. 面试官问:JS的this指向

    前言 面试官出很多考题,基本都会变着方式来考察this指向,看候选人对JS基础知识是否扎实.读者可以先拉到底部看总结,再谷歌(或各技术平台)搜索几篇类似文章,看笔者写的文章和别人有什么不同(欢迎在评论 ...

  5. 面试官问你JS基本类型时他想知道什么?

    面试的时候我们经常会被问答js的数据类型.大部分情况我们会这样回答包括:1.基本类型(值类型或者原始类型): Number.Boolean.String.NULL.Undefined以及ES6的Sym ...

  6. 面试官问线程安全的List,看完再也不怕了!

    最近在Java技术栈知识星球里面有球友问到了线程安全的 List: 扫码查看答案或加入知识星球 栈长在之前的文章<出场率比较高的一道多线程安全面试题>里面讲过 ArrayList 的不安全 ...

  7. 美团面试官问我一个字符的String.length()是多少,我说是1,面试官说你回去好好学一下吧

    本文首发于微信公众号:程序员乔戈里 public class testT { public static void main(String [] args){ String A = "hi你 ...

  8. 当阿里面试官问我:Java创建线程有几种方式?我就知道问题没那么简单

    这是最新的大厂面试系列,还原真实场景,提炼出知识点分享给大家. 点赞再看,养成习惯~ 微信搜索[武哥聊编程],关注这个 Java 菜鸟. 昨天有个小伙伴去阿里面试实习生岗位,面试官问他了一个老生常谈的 ...

  9. 当面试官问我ArrayList和LinkedList哪个更占空间时,我这么答让他眼前一亮

    前言 今天介绍一下Java的两个集合类,ArrayList和LinkedList,这两个集合的知识点几乎可以说面试必问的. 对于这两个集合类,相信大家都不陌生,ArrayList可以说是日常开发中用的 ...

  10. 【MySQL】面试官问我:MySQL如何实现无数据插入,有数据更新?我是这样回答的!

    写在前面 马上就是金九银十的跳槽黄金期了,很多读者都开始出去面试了.这不,又一名读者出去面试被面试官问了一个MySQL的问题:向MySQL中插入数据,如何实现MySQL中没有当前id标识的数据时插入数 ...

随机推荐

  1. 阿里云体验实验室 教你如何《快速搭建LNMP环境》

    ## 体验平台简介 面向开发者和中小企业打造的一站式.全云端的开发平台,打开浏览器就可以开发.调试.上线,所测即所得,并结合无服务器的模式,重新定义云原生时代的研发工作方法论.旨在降低开发者上手成本和 ...

  2. Core + Vue 后台管理基础框架9——统一日志

    1.背景 前阵子有园友留言,提到日志相关的东西,同时,最近圈子里也有提到日志这个东西.一个充分.集中的统一日志平台还是很有必要的,否则系统出问题了只能靠猜或者干瞪眼.何谓充分,日志记录满足最低要求.出 ...

  3. 当Notification和Websocket遇到https、http

    @ 目录 一.http转为https请求 (1)生成证书1(crt证书转tomcat使用的jks) (2)配置证书1 (3)生成证书2 (4)配置证书2 二.Websocket改为https连接 后言 ...

  4. await,async 我要把它翻个底朝天,这回你总该明白了吧

    一:背景 1. 讲故事 await,async 这玩意的知识点已经被人说的烂的不能再烂了,看似没什么好说的,但我发现有不少文章还是从理论上讲述了这两个语法糖的用法,懂得还是懂,不懂的看似懂了过几天又不 ...

  5. springboot之对之前的补充

    Spring Cloud 初级 一. Spring Boot 回顾   1 什么是 Spring Boot?   Spring Boot 是在 Spring 的基础之上产生的(确切的说是在 Sprin ...

  6. 程序员必知必会Git的小知识

    单人开发 1.初始化一个仓库 git init //cd到你工作的文件夹,初始化git仓库,默认会生成.git隐藏目录 2.配置自己的信息(Git 不喜欢不愿透漏姓名的人) git config -- ...

  7. el-dialog“闪动”解决办法

    问题描述:el-dialog关闭的时候总是出现两次弹窗 解决思路:既然是el-dialog产生的那就直接杀掉el-dialog 代码实践:在el-dialog上添加上一个v-if,值就是用闭窗的值,促 ...

  8. XMLHttpRequest 简单封装

    当开发简单页面的时候,不需要引入任何js库,这时需要封装一个用到 XMLHttpRequest 对象的好用的接口请求. simple 封装如下 ajaxRequest({ url: '', metho ...

  9. laravel核心Ioc容器

    laravel容器和依赖注入 啥是Ioc容器,方便我们实现依赖注入的一种实现,也就是说依赖注入不一定需要控制反转容器,只不过使用容器可能会方便些. laravel通过向容器中绑定接口的具体实现,可实现 ...

  10. 深入理解SVM,软间隔与对偶问题

    今天是机器学习专题的第33篇文章,我们继续来聊聊SVM模型. 在上一篇文章当中我们推到了SVM模型在线性可分的问题中的公式推导,我们最后得到的结论是一个带有不等式的二次项: \[\left\{\beg ...