参数处理器的作用
Mybatis作为一个ORM框架, 其最原始的本质就是JDBC,对于JDBC的使用步骤中有2步和参数处理器有关, 就是给预处理器PreparedStatement 设置参数以及通过结果集获取字段值。 这两个步骤在Mybatis中已经成为框架底层逻辑流程, 给用户留下扩展点的就是参数处理器。
参数处理器的顶级接口
/*T 为类型处理泛型*/
public interface TypeHandler<T> {
/**
*
* @param ps 预表达式
* @param i 参数位置
* @param parameter 实参对象
* @param jdbcType 对于的JDBC类型
* @throws SQLException
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* Gets the result.
*
* @param rs
* the rs
* @param columnName
* Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
* @return the result
* @throws SQLException
* the SQL exception
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
如代码所示, 对JDBC的使用有所了解, 为啥定义这几个方法还是比较容易知道了, 最后一个getResult 是和存储过程相关。
抽象类TypeReference
我们知道泛型类型如何获取, 是需要反射相关的代码来解析的, 所以Mybatis为了得到该参数处理器的泛型参数类型就搞了一个抽象类TypeReference, 用来解析实现类的泛型类型。
public abstract class TypeReference<T> {
/*泛型类型的原始类型*/
private final Type rawType;
protected TypeReference() {
/*构造方法解析泛型类型 注意这里的getClass 实际调用的是实现类的getClass, 方法具有多态性质*/
rawType = getSuperclassTypeParameter(getClass());
}
Type getSuperclassTypeParameter(Class<?> clazz) {
/*获取泛型父类*/
Type genericSuperclass = clazz.getGenericSuperclass();
/*如果父类是class对象*/
if (genericSuperclass instanceof Class) {
// try to climb up the hierarchy until meet something useful
//如果泛型父类和TypeReference 相等继续往上爬
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
if (rawType instanceof ParameterizedType) {
rawType = ((ParameterizedType) rawType).getRawType();
}
return rawType;
}
public final Type getRawType() { /*获取泛型类型*/
return rawType;
}
@Override
public String toString() {
return rawType.toString();
}
}
抽象实现类BaseTypeHandler
BaseTypeHandler 继承了TypeReference 实现了TypeHandler, 不仅是一个参数处理器,还能解析参数处理器的泛型类型。
BaseTypeHandler默认构造方法会掉用父类的无参构造方法,这也就调用了TypeReference的构造方法能够进行泛型类型参数解析。 作为一个抽象类,,其本身也实现父类的方法,并提供更加具体一点的抽象方法共给具体类去实现。
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
/**
* @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This field will remove future.
*/
@Deprecated
protected Configuration configuration;
/**
* Sets the configuration.
*
* @param c
* the new configuration
* @deprecated Since 3.5.0 - See https://github.com/mybatis/mybatis-3/issues/1203. This property will remove future.
*/
@Deprecated
public void setConfiguration(Configuration c) {
this.configuration = c;
}
@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, 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);
}
}
}
@Override
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);
}
}
@Override
public T getResult(ResultSet rs, int columnIndex) throws SQLException {
try {
return getNullableResult(rs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from result set. Cause: " + e, e);
}
}
@Override
public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
try {
return getNullableResult(cs, columnIndex);
} catch (Exception e) {
throw new ResultMapException("Error attempting to get column #" + columnIndex + " from callable statement. Cause: " + e, e);
}
}
public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* Gets the nullable result.
*
* @param rs
* the rs
* @param columnName
* Column name, when configuration <code>useColumnLabel</code> is <code>false</code>
* @return the nullable result
* @throws SQLException
* the SQL exception
*/
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;
public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;
}
是不是所有的参数处理器都要实现BaseTypeHandler
嗯, 最好是这样, 这样我们就可以很方便的获取泛型类实际类型,以及一些通用功能的实现, 但是参数处理器的接口本质还是TypeHandler, 其他类只是一些装饰而已。所以也可以直接继承TypeHandler。
参数处理器的配置(自定义参数处理器)
使用参数处理器我们需要指定这个参数处理器处理的Java类型,以及改Java类型对应JDBC类型。也就是配置参数处理器。对于配置我们一般都会使用XML或者注解指定。所以Mybatis也提供这样的功能。
实现自己的参数处理器:
/**
* 用户参数处理器
* @author puhaiguo
* @date 2022-12-13 04:58
* @version 1.0
*/
@MappedTypes({User.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class UserTypeHandler implements TypeHandler<User> {
@Override
public void setParameter(PreparedStatement ps, int i, User parameter, JdbcType jdbcType) throws SQLException {
int n = 0;
}
@Override
public User getResult(ResultSet rs, String columnName) throws SQLException {
return null;
}
@Override
public User getResult(ResultSet rs, int columnIndex) throws SQLException {
return null;
}
@Override
public User getResult(CallableStatement cs, int columnIndex) throws SQLException {
return null;
}
}
然后我们需要把这个类配置到Mybatis配置文件
因为我使用了注解标记了UserTypeHandler 对应的Java类型和JDBC类型, 所以我可以这么配置
<typeHandlers>
<typeHandler handler="com.learn.mybatis.typehandler.UserTypeHandler"/>
</typeHandlers>
如果没使用注解指明对应关系
需要这么配置:
<typeHandlers>
<typeHandler handler="com.learn.mybatis.typehandler.UserTypeHandler" jdbcType="User" javaType="varchar"/>
</typeHandlers>
参数处理器的解析过程
typeHandlerElement(root.evalNode("typeHandlers")); //设置类型处理器
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) {/*如果java类型为空*/
if (jdbcType == null) {/*如果jdbc类型为空*/
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
如上代码逻辑可以看出 支持直接配置类, 也可以指定包路径进行扫描。
因为我的配置类用的是注解,所以代码逻辑走typeHandlerRegistry.register(typeHandlerClass)
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class); //获取注解
if (mappedTypes != null) { //如果注解不为空
for (Class<?> javaTypeClass : mappedTypes.value()) { //获取注解的value值, 所有的java类型
register(javaTypeClass, typeHandlerClass); //进行注册
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {
register(getInstance(null, typeHandlerClass));
}
}
上面的代码也很好理解就是一些解析注解进行注册的过程。其中因为注解配置的JavaType 和 JDBCType都是数组类型,所以是支持多映射关系。
public void register(Class<?> javaTypeClass, Class<?> typeHandlerClass) {
register(javaTypeClass, getInstance(javaTypeClass, typeHandlerClass));
}
代码这里需要进行实例化参数处理器
public <T> TypeHandler<T> getInstance(Class<?> javaTypeClass, Class<?> typeHandlerClass) {//java类型 -> 类型处理器
if (javaTypeClass != null) {/*如果java类型不为空*/
try {
Constructor<?> c = typeHandlerClass.getConstructor(Class.class);//进行尝试找到当前java类型的构造函数
return (TypeHandler<T>) c.newInstance(javaTypeClass); //实例化对象
} catch (NoSuchMethodException ignored) {
// ignored 如果找不到那也不跑出异常
} catch (Exception e) {
throw new TypeException("Failed invoking constructor for handler " + typeHandlerClass, e);
}
}
try {
Constructor<?> c = typeHandlerClass.getConstructor();//无参实例化对象构造方法
return (TypeHandler<T>) c.newInstance();
} catch (Exception e) {
throw new TypeException("Unable to find a usable constructor for " + typeHandlerClass, e);
}
}
//Class对象也是继承Type的
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
MappedJdbcTypes mappedJdbcTypes = typeHandler.getClass().getAnnotation(MappedJdbcTypes.class); //获取类型处理类上的jdbc参数注解
if (mappedJdbcTypes != null) { //typeHandler 类上存在注解
for (JdbcType handledJdbcType : mappedJdbcTypes.value()) {//如果jdbc 集合类型不为空
register(javaType, handledJdbcType, typeHandler);
}
/*判断注解是否配置了支持空映射*/
if (mappedJdbcTypes.includeNullJdbcType()) {
register(javaType, null, typeHandler);
}
} else { //说明没有注解Jdbc
register(javaType, null, typeHandler);
}
}
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); //jdbctype 和类型处理器
typeHandlerMap.put(javaType, map); //存入映射
}
allTypeHandlersMap.put(handler.getClass(), handler); //所有的映射器
}
总结
整体流程差不多就是这样了, 并不多难, 主要还是java注解和反射技巧的使用。
走后在说一下, 我们在使用Mybatis的时候是不是很少会去定义类型处理器, 那么Mybatis为什么还是工作, 应为Mybatis一些基本的java类型与jdbc类型的类型处理器已经实现了且自动加载了。
TypeHandlerRegistry
public final class TypeHandlerRegistry {
private final Map<JdbcType, TypeHandler<?>> jdbcTypeHandlerMap = new EnumMap<>(JdbcType.class); /*类型处理器与Jdbc类型的对应关系*/
private final Map<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap = new ConcurrentHashMap<>();/*java类型与 jdbcTypeHandlerMap的对应关系*/
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;/*空的类型处理器Class对象*/
}
可以看见这个类是类型处理器的注册工厂。
其构造方法里面就注入了基本的类型处理器。
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());
register(Byte.class, new ByteTypeHandler());
register(byte.class, new ByteTypeHandler());
register(JdbcType.TINYINT, new ByteTypeHandler());
register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
register(Float.class, new FloatTypeHandler());
register(float.class, new FloatTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler());
register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler());
register(Reader.class, new ClobReaderTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new StringTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler());
register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
register(JdbcType.ARRAY, new ArrayTypeHandler());
register(BigInteger.class, new BigIntegerTypeHandler());
register(JdbcType.BIGINT, new LongTypeHandler());
register(BigDecimal.class, new BigDecimalTypeHandler());
register(JdbcType.REAL, new BigDecimalTypeHandler());
register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
register(InputStream.class, new BlobInputStreamTypeHandler());
register(Byte[].class, new ByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
register(byte[].class, new ByteArrayTypeHandler());
register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.BLOB, new BlobTypeHandler());
register(Object.class, unknownTypeHandler);
register(Object.class, JdbcType.OTHER, unknownTypeHandler);
register(JdbcType.OTHER, unknownTypeHandler);
register(Date.class, new DateTypeHandler());
register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
register(JdbcType.TIMESTAMP, new DateTypeHandler());
register(JdbcType.DATE, new DateOnlyTypeHandler());
register(JdbcType.TIME, new TimeOnlyTypeHandler());
register(java.sql.Date.class, new SqlDateTypeHandler());
register(java.sql.Time.class, new SqlTimeTypeHandler());
register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());
register(Instant.class, new InstantTypeHandler());
register(LocalDateTime.class, new LocalDateTimeTypeHandler());
register(LocalDate.class, new LocalDateTypeHandler());
register(LocalTime.class, new LocalTimeTypeHandler());
register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
register(OffsetTime.class, new OffsetTimeTypeHandler());
register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
register(Month.class, new MonthTypeHandler());
register(Year.class, new YearTypeHandler());
register(YearMonth.class, new YearMonthTypeHandler());
register(JapaneseDate.class, new JapaneseDateTypeHandler());
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
}
在配置类加载的时候会创建类型处理器对象。
到这里 类型处理器的使用和解析已经结束了, 但是注入处理器注册器工厂之后在Mybatis怎么在执行sql的使用调用参数处理器下篇博文在继续了解。