Jdbc的类型与java的数据类型并不完全对应,所以在PreparedStatement为sql进行绑定参数的时候,需要从java类型转换为jdbc类型,而从结果集中获取数据的时候,需要将jdbc类型转换为java类型,mybatis使用类处理器解决上述问题。
代码分析
以java的Integer类型与jdbc对应的int类型进行解析。对应mybatis中的IntegerTypeHandler,其继承关系如下:
TypeHandler接口
typeHandler接口中定义了四个方法,但是在功能上可以分为两类:
public interface TypeHandler<T> {
//数据入库之前时数据处理,通过PreparedStatement对象设置SQL参数的时候使用的具体方法,,其中i是参数在SQL的下标,parameter是参数,jdbcType是数据库类型
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
//下面三个是从数据库加载数据后,vo对象封装前的数据处理,是从JDBC结果集中获取数据进行转换,要么使用列名,(columnname)要么使用下标(columnIndex)获取数据库的数据,其中最后一个getResult方法是存储过程使用的。
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
BaseTypeHandler
TypeHandler使用了模板设计方法,实现了TypeHandler接口,在baseTypeHandler中实现逻辑的大致框架,具体的处理交由子类实现。讲其中的两个方法的实现。
//当parameter和jdbcType都为空时将抛出异常,如果能明确jdbcType,将会进行空设置(PreparedStatement的setNull方法);如果paramter不为空,将使用setNonNullParameter去设置参数(该方法需要子类去实现)
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, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}
//非空结果集是通过getNullableResult方法去获取的,该方法需要子类去实现,同样是针对下标、列名以及存储过程三种的实现
public T getResult(ResultSet rs, String columnName) throws SQLException {
try {
return getNullableResult(rs, columnName);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set. Cause: " + e, e);
}
}
IntegerTypeHandler
继承BaseTypeHandler,实现BaseTypeHandler中的抽象方法。代码简单。
@Override
//PreparedStatement 设置参数
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}
@Override
//ResultSet获取结果
public Integer getNullableResult(ResultSet rs, String columnName)
throws SQLException {
int result = rs.getInt(columnName);
return result == 0 && rs.wasNull() ? null : result;
}
TypeHandler的注册以及获取
TypeHandlerRegistry类负责对TypeHandler进行管理,首先先看下mybatis解析XML配置的typeHandlers节点。
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
//子节点为typeHandler时, 可以指定javaType属性, 也可以指定jdbcType, 也可两者都指定
//javaType 是指定java类型
//jdbcType 是指定jdbc类型(数据库类型: 如varchar)
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);
//注册typeHandler, typeHandler通过TypeHandlerRegistry这个类管理
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
这个主要是解析XML配置TypeHandler属性,将其交给typeHandlerRegistry进行管理。在mybatis初始化的时候,会为所有已知的类型处理器 TypeHandler 创建对象,同时注册在typeHandlerRegistry上。比如int类型的处理
//在对Integer和int与IntegerTypeHandler注册时,采用java type + handler的形式,注册时首先判断TypeHandler是否有MappedJdbcTypes注解指明jdbc类型
//有则使用java type + jdbc type + handler模式进行三者联合注册,没有则将jdbcType置为null然后进行三者联合注册
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
注册的核心方法:
//java type + jdbc type + handler
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
//javaType对应的【JdbcType和TypeHandler】的映射关系
Map<JdbcType, TypeHandler<?>> map = typeHandlerMap.get(javaType);
if (map == null || map == NULL_TYPE_HANDLER_MAP) {
map = new HashMap<>();
typeHandlerMap.put(javaType, map);
}
map.put(jdbcType, handler);
}
allTypeHandlersMap.put(handler.getClass(), handler);
}
注册完成后,TypeHandlerRegistry还提供了查找的方法,可以根据根据 Java 类型和 Jdbc 类型查找对应的 TypeHandler 对象。
TypeAliasRegistry
mybatis中允许我们使用别名进行注册,TypeAliasRegistry进行别名的管理,其主要是别名的注册和解析,在构造函数中实现通用的别名注册。
public <T> Class<T> resolveAlias(String string) { try { if (string == null) { return null; } // issue #748 //转换为小写 String key = string.toLowerCase(Locale.ENGLISH); Class<T> value; if (typeAliases.containsKey(key)) { value = (Class<T>) typeAliases.get(key); } else { value = (Class<T>) Resources.classForName(string); } return value; } catch (ClassNotFoundException e) { throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e); } }
自定义注册TypeHandler
有两种实现方式,一种是实现TypeHandler接口,另一种是继承BaseTypeHandler。我们使用第一种方式实现:
@MappedTypes(String.class)//定义的是JavaType类型,可以指定哪些Java类型被拦截; @MappedJdbcTypes(JdbcType.BIGINT)//定义的是JdbcType类型,取值范围在枚举类JdbcType中能找到 public class DogTypeHandler implements TypeHandler<String> { @Override public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { } @Override public String getResult(ResultSet rs, String columnName) throws SQLException { return null; } @Override public String getResult(ResultSet rs, int columnIndex) throws SQLException { return null; } @Override public String getResult(CallableStatement cs, int columnIndex) throws SQLException { return null; } }
最后在使用的时候,指定typehandler即可。