Mybatis在执行SQL语句之前和完成之后都将要处理JDBC类型与Java类型之间的转换,简言之TypeHandler主要的工作就是完成JDBC类型和Java类型之间的相互转换,具体情况如下:
- 执行SQL语句之前为PreparedStatement对象参数占位符赋值,具体调用PreparedStatement接口中提供的一系列setter方法,将Java类型转换为对应的JDBC类型;
- 执行完成SQL语句获取到ResultSet对象之后,调用ResultSet提供的getter方法将JDBC类型转换为Java类型。
TypeHandler使用模板方法设计模式,TypeHandler接口定义了相关设置参数和获取执行SQL语句完成后结果的一些方法,BaseTypeHandler抽象类实现了TypeHandler接口定义方法的默认实现,并重新定义了赋值和取值的方法,其实现逻辑交给具体实现类来实现。Mybatis提供了常规数据类型的TypeHandler,如果想要实现自定义TypeHandler,通过继承BaseTypeHandler抽象类即可。
Mybatis中默认实现了很多TypeHandler,如StringTypeHandler、BigDecimalTypeHandler和BooleanTypeHandler等TypeHandler。从BigDecimalTypeHandler源代码可以看出,BigDecimalTypeHandler实现了BaseTypeHandler定义了四个抽象方法,BigDecimalTypeHandler#setNonNullParameter()方法调用PreparedStatement对象的setBigDecimal()方法将BigDecimal类型转换为对应的JDBC类型,并为参数占位符赋值。getNullableResult()方法调用ResultSet对象的getBigDecimal()方法将JDBC中的字符串类型转为BigDecimal类型,并返回列的值。
public class BigDecimalTypeHandler extends BaseTypeHandler<BigDecimal> {
@Override
public void setNonNullParameter(
PreparedStatement ps, int i, BigDecimal parameter, JdbcType jdbcType)
throws SQLException {
ps.setBigDecimal(i, parameter);
}
@Override
public BigDecimal getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getBigDecimal(columnName);
}
@Override
public BigDecimal getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getBigDecimal(columnIndex);
}
@Override
public BigDecimal getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getBigDecimal(columnIndex);
}
}
TypeHandlerRegistry
TypeHandlerRegistry定义了JDBC类型、Java类型与TypeHandler之间的映射关系,如下源代码所示,
public final class TypeHandlerRegistry {
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap =
new EnumMap<>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap =
new ConcurrentHashMap<>();
private final TypeHandler<Object> unknownTypeHandler;
private final Map<Class<?>, TypeHandler<?>> allTypeHandlersMap =
new HashMap<>();
private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP =
Collections.emptyMap();
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;
}
TypeHandlerRegistry使用Map数据结构来定义了JDBC类型、Java类型与TypeHandler之间的关系,分别使用jdbcTypeHandlerMap和typeHandlerMap属性存储对应的关系定义。
TypeHandlerRegistry以构造方法的形式来完成默认JDBC类型、Java类型与TypeHandler关系的定义,并实现getTypeHandler和hasTypeHandler方法对外提供根据Java类型或者JDBC类型获取对应的TypeHandler对象,以及判断Java类型是否有对应的TypeHandler处理对象。
public TypeHandlerRegistry(Configuration configuration) {
this.unknownTypeHandler = new UnknownTypeHandler(configuration);
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
// 省略其它代码
}
实例
以DefaultParameterHandler#setParameters为例,当通过SimpleExecutor类doQuery方法完成SQL语句执行之前,会先调用DefaultParameterHandler类的setParameters方法来完成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)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 如果类型处理器中有对应的typeHandler,直接赋值
value = parameterObject;
} else {
// 如果没有对应的typeHandler,将参数转化为MetaObject反射工具类
// 然后从元数据类中寻找当前参数的值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 获取typeHandler类型处理器
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 获取JdbcType Java类型定义
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 调用typeHandler的setParameter方法完成占位符参数的赋值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
DefaultParameterHandler#setParameters源代码虽然篇幅很大,但是其核心逻辑并不复杂:
- 从BoundSql类中获取当前SQL语句中的占位符参数ParameterMapping定义,如果不为空就直接遍历ParameterMapping定义,为每一个占位符参数赋值;
- 如果占位符参数的ParameterMode不是OUT就进行赋值处理;
- 如果在类型处理器中找到对应的typeHandler,就直接赋值;
- 如果在没有找到对应的typeHandler,将参数转化为MetaObject反射工具类,然后从元数据类中寻找当前参数的值;
- 得到value值之后,再获取在ParameterMapping中定义的typeHandler类型处理器和Java类型;
- 调用typeHandler类型处理器的setParameter方法,将value值设置到当前的PreparedStatement对象。