MyBatis源码解析之基础模块—TypeHandler
前文回顾
上一章节我们一起分析了Mybatis的Plugin模块的源码。掌握了如何配置拦截器注解,如何自定义拦截器以及拦截器的执行过程。
在使用Mybatis的过程中,基本上我们都要在xml中编写相应的sql语句以及对应的java属性与字段的转换。那么对于数据库与java之间的转换,Mybatis是怎么做的呢?
接下来本章节我们对MyBatis Type模块类型转换的源码进行分析。
架构设计
按惯例,咱们先了解下Type模块的总体架构设计。
Type模块所在包路径为org.apache.ibatis.type
,其对应的类架构设计图如下:
以上为Type模块的架构逻辑,当然针对不同的类型转换实现,架构图中只展示了IntegerTypeHandler、UnknownTypeHandler两个典型实现。
基于架构图,接下来逐个分析其实现源码。
源码解读
JdbcType
JdbcType就是一个枚举类。该类定义了常用的一些数据类型,比如Integer,Double,Date,Date等,基本上满足了我们开发中常用的数据类型。
package org.apache.ibatis.type;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
public enum JdbcType {
INTEGER(Types.INTEGER),
BIGINT(Types.BIGINT),
FLOAT(Types.FLOAT),
REAL(Types.REAL),
DOUBLE(Types.DOUBLE),
NUMERIC(Types.NUMERIC),
DECIMAL(Types.DECIMAL),
CHAR(Types.CHAR),
VARCHAR(Types.VARCHAR),
DATE(Types.DATE),
BOOLEAN(Types.BOOLEAN)
……
; // JDBC 4.2 JDK8
public final int TYPE_CODE;
private static Map<Integer,JdbcType> codeLookup = new HashMap<>();
static {
for (JdbcType type : JdbcType.values()) {
codeLookup.put(type.TYPE_CODE, type);
}
}
JdbcType(int code) {
this.TYPE_CODE = code;
}
public static JdbcType forCode(int code) {
return codeLookup.get(code);
}
}
MappedTypes
该注解接口作用于类型转换的实现类,用于标注要映射的java类型。
public @interface MappedTypes {
/**
* 返回要映射处理的java类型集合
*/
Class<?>[] value();
}
MappedJdbcTypes
该注解接口作用于类型转换的实现类,用于标注要映射的数据库类型。
public @interface MappedJdbcTypes {
/**
* 返回要映射处理的jdbc类型集合
*/
JdbcType[] value();
/**
* 返回是否映射空值 默认false
*/
boolean includeNullJdbcType() default false;
}
关于MappedTypes、MappedJdbcTypes的使用,可参考源码测试中的StringTrimmingTypeHandler类:
@MappedTypes(String.class)
@MappedJdbcTypes(value={
JdbcType.CHAR,JdbcType.VARCHAR}, includeNullJdbcType=true)
public class StringTrimmingTypeHandler implements TypeHandler<String> {
//方法实现ain略
}
TypeReference
TypeReference的核心功能是获取类型转换实现类的父类泛型参数类型,听起来貌似有点绕😊。在转换实现类(比如IntegerTypeHandler)在实例化时,会调用TypeReference的构造函数,而该构造函数中会执行获取父类泛型参数类型的方法getSuperclassTypeParameter()。类的详细说明请参看源码注释说明:
package org.apache.ibatis.type;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public abstract class TypeReference<T> {
//原生类型
private final Type rawType;
//构造函数,设置原生类型
protected TypeReference() {
rawType = getSuperclassTypeParameter(getClass());
}
/**
* 功能描述:根据当前类的Class信息获取超类泛型的参数类型(比如IntegerHandlerType的超类泛型参数为Integer)
* @param clazz
* @return
*/
Type getSuperclassTypeParameter(Class<?> clazz) {
Type genericSuperclass = clazz.getGenericSuperclass();
//如果传入类的泛型父类为Class的实例且不为TypeReference类,则已clazz的父类为参数递归调用getSuperclassTypeParameter;否则抛出异常
if (genericSuperclass instanceof Class) {
// try to climb up the hierarchy until meet something useful
if (TypeReference.class != genericSuperclass) {
return getSuperclassTypeParameter(clazz.getSuperclass());
}
throw new TypeException("'" + getClass() + "' extends TypeReference but misses the type parameter. "
+ "Remove the extension or add a type parameter to it.");
}
Type rawType = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0];
// TODO remove this when Reflector is fixed to return Types
// 此处貌似说在反射模块中的Reflector修复后会删除如下逻辑(存疑)
if (rawType instanceof ParameterizedType) {
rawType = ((ParameterizedType) rawType).getRawType();
}
return rawType;
}
//获取构造方法中设置的原生类型
public final Type getRawType() {
return rawType;
}
//toString方法返回rawType的toString方法
@Override
public String toString() {
return rawType.toString();
}
}
TypeHandler
TypeHandler为类型转换的核心接口,该接口提供四个方法。
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public interface TypeHandler<T> {
//设置参数
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
* 根据ResultSet及columnName获取转换结果
* 请注意:当configuration中的useColumnLabel=false生效,useColumnLabel默认为true(请参看Configuration中的useColumnLabel属性)
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
//根据ResultSet及columnIndex索引获取转换结果
T getResult(ResultSet rs, int columnIndex) throws SQLException;
//根据CallableStatement及columnIndex索引获取转换结果
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
BaseTypeHandler
BaseTypeHandler 该类为抽象类,其继承TypeReference并实现TypeHandler,并且采用模板方法的设计模式,实现了TypeHandler的接口方法的通用逻辑,而相关实现细节则调用定义的抽象方法。由具体的类型转换实现类来实现该方法。
package org.apache.ibatis.type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.executor.result.ResultMapException;
import org.apache.ibatis.session.Configuration;
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
/**
* 设置参数
* 1、若parameter为空:
* 1.1、若jdbcType为空,则抛出异常
* 1.2、ps根据索引位置设置对应的字段为空
* 2、若parameter不为空,调用非空参数设置方法进行参数设置
* @param ps
* @param i
* @param parameter
* @param jdbcType
* @throws SQLException
*/
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: " + e, e);
}
} else {
try {
setNonNullParameter(ps