本篇主要介绍一下 mybatis 的类型处理器,基于3.4.6版本。
知识点
- 什么是类型处理器
- 类型处理器的作用
- 如何自定义类型处理器
- 实现原理
什么是类型处理器
我们平时在使用 mybatis 的时候比较简单,只要写一个 sql 语句,然后定义好对应的接口就可以使用了,这里就涉及到一个问题:我们调用接口的时候传入的明明是 java 的数据类型,为什么能够正常存到数据库里呢,我们从数据库里取出来的明明是数据库中的类型,为什么能够直接在代码里拿到 java 类型呢?类型处理器就是来解决这个问题的,用 mybatis 的术语,就是 TypeHandler。
类型处理器的作用
其实上面也说到了,类型处理器的作用非常简单,总结起来就2种
- 将我们传入接口的参数转换为对应的数据库类型
- 将数据库种查询回来的字段类型转换为对应的java类型
如何自定义类型处理器
我们知道 mybatis 内置了很多类型的处理器,这也是我们什么都不做,直接能够完成类型转换的原因,看下目前有哪些类型处理器
可以看到,type目录下几乎全是,有兴趣的可以自己去看下,这里不多做介绍。如果遇到了内置处理器无法处理的新类型,或者我们想要在内置的类型处理器上加一些自己的逻辑,怎么办呢?这时就需要自定义类型处理器了。这个比较简单,其实官方文档里也介绍过了,这里再详细介绍一下
1) 继承 BaseTypeHandler ,我这里处理的是String 类型
public class MyTypeHandler extends BaseTypeHandler {
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, s);
}
@Override
public String getNullableResult(ResultSet resultSet, String s) throws SQLException {
return null;
}
@Override
public String getNullableResult(ResultSet resultSet, int i) throws SQLException {
return null;
}
@Override
public String getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return null;
}
}
2)最简单的情况下上面定义已经可以了,我们还可以自己指定处理器处理的数据库字段类型,通过@MappedJdbcTypes
注解来指定,比如我这里要处理的是表中的 varchar 类型数据,如果不指定则默认取 key 为 null 的对应jdbc类型处理器
@MappedJdbcTypes(value = JdbcType.VARCHAR)
3) 另外还可以通过@MappedTypes
注解来指定处理对应的 java 类型,如果没指定,则默认取 BaseTypeHandler 中对应的泛型类的实际类型
@MappedTypes(value = String.class)
4) 最后需要在配置中指定类型处理器的路径
mybatis.type-handlers-package=com.example.mybatisanalyze.typehandler
实现原理
注册流程
自定义一个类型处理器如此简单,它的实现原理也不难,我们来看下是如何实现的。这里先看一个非常重要的类TypeHandlerRegistry
,该类就是类型处理器管理类,所有的类型处理器都在这里面维护着
可以看到在构造函数里就会注册内置的类型处理器。在我们配置了对应的类型处理器所在包路径之后,mybatis-spring 或者 mybatis就会调用对应的注册接口进行注册
这里面的逻辑比较简单,就不细说,知道一点就可以:里面维护的 map 结构是
处理流程
对于处理流程,要调试的话,关键看一个类:BaseTypeHandler。这里用到了模板模式,所有的处理器都要先经过基类的模板处理,再调子类的定制化逻辑。它继承的是TypeHandler
从对应的接口名称中我们也能看出来,setParameter 是用来将 java 类型转换为表字段类型的,其他的是用来讲表字段类型转换为 java 类型的。我们平时在执行逻辑的时候用的是DefaultSqlSession
,以selectOne
函数来举例看下是在哪里做类型转换处理的。从org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)
一直跟进去
这里默认用的缓存执行器,也就是CachingExecutor
。继续跟进去
缓存不存在的话,会使用 BaseExecutor 来执行查询,中间的链路不多介绍了,直接看org.apache.ibatis.executor.SimpleExecutor#prepareStatement
在这里做参数的类型处理,跟进到org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
可以看到这里就是遍历参数,根据参数类型获取对应的类型处理器逐个处理。
再来看下对于数据库查回的结果集是如何做处理的,看下如下逻辑org.apache.ibatis.executor.statement.PreparedStatementHandler#query
这里会调用对应的连接池执行数据库操作,然后进行结果处理。然后直接跟到org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap)
这里开始获取对应的行数据,我画红框的地方就是表字段和 Java 类型字段映射处理逻辑,跟进去看下
这里又出现了 TypeHandler,这里就是具体的类型处理器对结果进行处理的逻辑。
总结
本文详细介绍了 mybatis 的类型处理器,看完你会发现原来类型处理器实现也是很简单的。