这个handler的主要功能就是完成mysql的列值与java字段相互转换的处理器。那么这里就会出现两种不同的类型。
- mysql的列类型叫做jdbcType(由jdbc提供);
- java的字段类型叫做javaType(由jdk提供);
首先我们先看一段熟悉的代码
public static void main(String[] args) {
Connection conn = DriverManager.getConnection(url,user, password)
String sql = "select name from student where id = ?";
PreparedStatement pst = conn.prepareStatement(sql);
// 1.java转jdbc
pst.setInt(1,10);
ResultSet rs = pst.executeQuery();
while(rs.next()) {
// 2.jdbc转java
String name = rs.getString("name");
// 3.想对出参附加"小一班"前缀
String schoolName = "小1班--" + name;
}
}
看到了吧,其实jdbc和java之间的互转,说的就是:
- 当我java转jdbc时,我是调用jdbc的setInt()呢还是setString()?
- 当我jdbc转java时,我是调用jdbc的getInt()呢还是getString()?
那么如何确定是调用哪个方法呢?那么就有了typeHandler.这里要说明一点:mybatis查出的每个mysql的列字都需要一个typeHandler才能知道调用jdbc的哪个方法.那么获取typeHandler理论上需要2个条件,javaType、jdbcType.
首先我们来看一下TypeHandlerRegistry的结构
public final class TypeHandlerRegistry {
// java类型 -> mysql类型 -> TypeHandler.很常用.
// 根据javaType和jdbcType获取对应的TypeHandler时就是使用的该map
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap
= new ConcurrentHashMap<>();
// mysql类型 -> TypeHanlder. 暂时未找到使用的地方
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap
= new EnumMap<>(JdbcType.class);
// 当无法获取typeHandler时,暂时绑定的就是该typeHandler
private final TypeHandler<Object> unknownTypeHandler;
// 快速根据我们写的TypeHandler获取到该typeHandler对象,不用创建新的typeHandler对象
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap = new HashMap<>();
}
当TypeHandlerRegistry初始化完毕,typeHandlerMap、jdbcTypeHandlerMap、allTypeHandlersMap,具体是什么样呢?请看下图:
从上图可以看出,typeHandlerMap是最重要的typeHandler推断的map.只要给出javaType和jdbcType就能推断出typeHandler是什么,有时就算没有给出jdbcType也能根据jdbcType=null进行推断.那么TypeHandlerRegistry是如何完成那么多typeHandler的注册与推断的呢?请看下文。
1. typeHandler的注册
1.1.mybatis默认typeHandler注册
默认typeHandler注册的地方只有一处,就是TypeHandlerRegistry对象初始化的时候.
public final class TypeHandlerRegistry {
private final Map<Type, Map<JdbcType, TypeHandler<?>>>
typeHandlerMap = new ConcurrentHashMap<>();
private final Map<JdbcType, TypeHandler<?>>
jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class);
private final Map<Class<?>, TypeHandler<?>>
allTypeHandlersMap = new HashMap<>();
private final TypeHandler<Object> unknownTypeHandler;
// 构造器
public TypeHandlerRegistry(Configuration configuration) {
this.unknownTypeHandler = new UnknownTypeHandler(configuration);
register(JdbcType.INTEGER, new IntegerTypeHandler());
// 这里是注册到typeHandlerMap与allTypeHandlersMap,形成的结构是
// String.class --> null --> StringTypeHandler
register(String.class, new StringTypeHandler());
// 这里也是注册到typeHandlerMap与allTypeHandlersMap,形成的结构是
// String.class --> JdbcType.VARCHAR --> StringTypeHandler
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
// 这里是注册到jdbcTypeHandlerMap中,形成的结构是
// JdbcType.VARCHAR --> StringTypeHandler
register(JdbcType.VARCHAR, new StringTypeHandler());
// ... 以下省略
}
}
以上代码执行会形成下面的结构(简单举例并非全部默认数据):
1.2.自定义typeHandler集中注册
自定义typeHandler举例:
@MappedTypes(value = {String.class})
@MappedJdbcTypes(value = {JdbcType.VARCHAR},includeNullJdbcType = true)
public class SexTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps,
int i, String parameter, JdbcType jdbcType) throws SQLException {
// 性别:1、男,2、女
int sex = parameter.equals("1") ? 1 : 2;
ps.setInt(i,sex);
}
@Override
public String getNullableResult(ResultSet rs,
String columnName) throws SQLException {
int sex = rs.getInt(columnName);
return sex == 1 ? "男" : "女";
}
@Override
public String getNullableResult(ResultSet rs,
int columnIndex) throws SQLException {
return null;
}
@Override
public String getNullableResult(CallableStatement cs,
int columnIndex) throws SQLException {
return null;
}
}
此类typeHandler的注册都是在mybatis的核心配置文件mybatis-config.xml中配置。自定义typeHandler的注册规则如下:
- 规则一:标签中明确定义了javaType、jdbcType、typeHandler属性,mybatis会根据明确定义的javaType、jdbcType、typeHandler注册到TypeHandlerRegistry。
// TypeHandlerRegistry
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
typeHandlerMap.put(javaType, map);
}
// 注册到allTypeHandlers
allTypeHandlersMap.put(handler.getClass(), handler);
}
- 规则二:标签中只定义了javaType、typeHandler没有定义jdbcType,mybatis会根据明确定义的javaType、@MappedJdbcTypes注解中标明的jdbcType(包含includeNullJdbcType属性)取笛卡尔积,分别注册到TypeHandlerRegistry。
// TypeHandlerRegistry
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
// 获取@MappedJdbcTypes(value = {JdbcType.VARCHAR},
// includeNullJdbcType = true)标签里标明的jdbcType属性
MappedJdbcTypes mappedJdbcTypes =
typeHandler.getClass().getAnnotation(MappedJdbcTypes.class);
if (mappedJdbcTypes != null) {
// javaType 与 jdbcType取笛卡尔积进行注册.
for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {
// 调用【规则一】中代码
register(javaType, handledJdbcType, typeHandler);
}
// 注册javaType --> jdbcType = null --> typeHandler的数据进来
// @MappedJdbcTypes中includeNullJdbcType属性
if (mappedJdbcTypes.includeNullJdbcType()) {
// 调用【规则一】中代码
register(javaType, null, typeHandler);
}
} else {
// 调用【规则一】中代码
register(javaType, null, typeHandler);
}
}
- 规则三:标签中只定义了typeHandler,没有明确定义javaType、jdbcType.mybatis会根据typeHanlder上的@MappedTypes注解(如果没有该注解mybatis会根据该类中的泛型来确定javaType)中标明的javaType与@MappedJdbcTypes注解中标明的jdbcType(包含includeNullJdbcType属性)取笛卡尔积,分别注册到TypeHandlerRegistry。
// TypeHandlerRegistry
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
// 获取@MappedTypes(value = {String.class})标签里标明的javaType属性.
MappedTypes mappedTypes
= typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
// 遍历@MappedTypes中标明的所有javaType属性,看到了吧,
// 这里与【规则二】中的代码构成了javaType与jdbcType
// 确定typeHandler的笛卡尔积注册规则
for (Class<?> javaTypeClass : mappedTypes.value()) {
// 调用【规则二】中代码
register(javaTypeClass, typeHandlerClass);
mappedTypeFound = true;
}
}
// 如果没有标明@MappedTypes注解,
// 根据typeHandler中定义的泛型去推断javaType
if (!mappedTypeFound) {
register(getInstance(null, typeHandlerClass));
}
}
public <T> void register(TypeHandler<T> typeHandler) {
boolean mappedTypeFound = false;
// 1.又找了一遍@MappedTypes注解
MappedTypes mappedTypes =
typeHandler.getClass().getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> handledType : mappedTypes.value()) {
register(handledType, typeHandler);
mappedTypeFound = true;
}
}
// 2.根据typeHandler的泛型作为javaType去注册
if (!mappedTypeFound && typeHandler instanceof TypeReference) {
try {
TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
// 这里就是typeHandler的泛型
register(typeReference.getRawType(), typeHandler);
mappedTypeFound = true;
} catch (Throwable t) {
// maybe users define the TypeReference with a different type
// and are not assignable, so just ignore it
}
}
if (!mappedTypeFound) {
register((Class<T>) null, typeHandler);
}
}
1.2.2.单个注册
<configuration>
<typeHandlers>
<!--如果定义了别名扫描,这里就不用写类的全限定名,只需要类名全小写即可-->
<typeHandler handler="sextypehandler" javaType="string" jdbcType="VARCHAR"/>
</typeHandlers>
</configuration>
单个类注册的规则:因可以明确指定javaType、jdbcType,所以参考规则一、规则二、规则三。
// XMLConfigBuilder
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);
}
}
}
}
}
1.3.ResultMap中typeHandler注册
<resultMap id="calendarItemMap" type="calendaritem">
<result property="sex" column="sex" typeHandler="sextypehandler"></result>
</resultMap>
这里只会把SexTypeHandler对象存放到ResultMapping结构中,不会注册到TypeHandlerRegistry中.具体看typeHandler与字段的绑定.
2.typeHandler与字段的绑定
2.1.javaType转jdbcType
该类typeHandler就是当我们的sql语句中有参数,需要用PreparedStatement去set参数的时候需要确定的typeHandler.该类typeHandler全是TypeHandlerRegistry中的unknownTypeHandler,因为我们set参数的时候是无法知道对应的jdbcType是什么的,而且typeHandler与MappedStatement属于不同的结构体,而且unknownTypeHandler结构中有个再次确认typeHandler的机制,当执行set的时候,根据我们的javaType --> null --> typeHandler的方式寻找set时对应的typeHandler.
// DefaultParameterHandler
@Override
public void setParameters(PreparedStatement ps) {
// 省略..
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) {
// 省略..
// 这里是获取该参数的typeHandler,这里的handler为unknownTypeHandler
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 获取jdbcType = null
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 这里调用unknownTypeHandler的方法
// 因为PrepareStatement的set***和get***方法都是下标
// 从1开始的,这里循环是从0开始的,所以需要i+1
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: "
+ parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
// UnknownTypeHandler
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
Object parameter, JdbcType jdbcType) throws SQLException {
// 二次推断typeHandler
TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
handler.setParameter(ps, i, parameter, jdbcType);
}
2.2.jdbcType转javaType
2.2.1.写明property与column映射到typeHandler的绑定
该类不需要任何的typeHandler推断,只需要在找到该字段对应的ResultMapping时,调用相应typeHandler的get或set方法即可
2.2.2.未写明property与column映射到typeHandler的绑定
该类需要typeHandler推断,还记得开篇地方说的mybatis查询的列转换成java字段,都需要一个typeHandler吗,如果我们ResultMap的property中未标明typeHandler程序怎么处理的呢?当mybatis生成ResultMap结构后还有还有一步二次确认typeHandler的过程.
// ResultMapping
public ResultMapping build() {
// lock down collections
resultMapping.flags = Collections.unmodifiableList(resultMapping.flags);
resultMapping.composites =
Collections.unmodifiableList(resultMapping.composites);
// 二次确认typeHandler
resolveTypeHandler();
// 校验
validate();
return resultMapping;
}
private void resolveTypeHandler() {
// 如果resultMap中指明了typeHandler,typeHandler那么对应字段的解析就不会走这里,
// 大部分情况是需要走这里的,从而可以看出,这里就是对未指明typeHandler的字段
// 进行typeHandler推断
if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
Configuration configuration = resultMapping.configuration;
TypeHandlerRegistry typeHandlerRegistry
= configuration.getTypeHandlerRegistry();
// 根据javaType,jdbcType推断.结构见图1
resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(
resultMapping.javaType, resultMapping.jdbcType);
}
}
推断举例:
// java类
public class CalendarItem {
private String contentId;
private Integer contentType;
private Integer id;
// getter、setter省略
}
<resultMap id="calendarItemMap" type="calendaritem"> <!--别名不在赘述-->
<result property="contentId" javaType="string" column="content_id" jdbcType="JdbcType.VARCHAR"/1. property="contentId"的result,这里因为写了javaType="string",jdbcType="JdbcType.VARCHAR".看图一,mybatis能很轻松的通过key1=String.class,key2=JdbcType.VARCHAR很快能定义出StringTypeHandler
2. property="contentType"的result,因为这里顶一个javaType="int".看图一,虽然没有写jdbcType,mybatis也能很轻松的通过key1=Integer.class,javaType=null,很快能定义出IntegerTypeHandler
3. property="id",这里javaType和jdbcType都没有写,那么mybatis是如何对id字段绑定typeHandler呢?看到<id>标签的父标签<resultMap>的type="calendaritem"了吗?没错这里就是**根据CalendarItem.class的java字段的id属性来推断出**<id>标签对应的javaType为Integer.class(具体怎么推断的这里不展开讲,后面会在ResultMap解析的时候说明).那么mybatis又能通过<Intgeger.class,null>找到对应的IntegerTypeHandler.>
<result property="contentType" javaType="int" column="content_type"></result>
<id property="id" column="id"></id>
</resultMap>
- property="contentId"的result,这里因为写了javaType="string",jdbcType="JdbcType.VARCHAR".看图一,mybatis能很轻松的通过key1=String.class,key2=JdbcType.VARCHAR很快能定义出StringTypeHandler
- property="contentType"的result,因为这里顶一个javaType="int".看图一,虽然没有写jdbcType,mybatis也能很轻松的通过key1=Integer.class,javaType=null,很快能定义出IntegerTypeHandler
- property="id",这里javaType和jdbcType都没有写,那么mybatis是如何对id字段绑定typeHandler呢?看到<id>标签的父标签<resultMap>的type="calendaritem"了吗?没错这里就是根据CalendarItem.class的java字段的id属性来推断出<id>标签对应的javaType为Integer.class(具体怎么推断的这里不展开讲,后面会在ResultMap解析的时候说明).那么mybatis又能通过<Intgeger.class,null>找到对应的IntegerTypeHandler.
本文暂时没有评论,来添加一个吧(●'◡'●)