TypeHandler类型转换器
1,概述:
我们思考一个问题,我们之前在是使用JDBC的时候,我们从ResultSet获取对应的数据,用到了对应getXXX方法。比如获取一个数据库为int的数据,我们可以使用rs.getInt(),如果我们获取数据库类型为varchar的数据,我们可以使用rs.getString()。
而我们现在使用了Mybatis之后,从数据库里面查询出来了之后,他是如何把数据库的类型和Java类型对应起来的呢?这个地方Mybatis就用到了typeHandler来实现。
2,设计思路:
在typeHander里面提出了两个类型。一个叫做jdbcType,一个叫做javaType。jdbcType表示数据库中的字段类型,另外一个javaType表示对应的PO中的java的类型。而我们的typeHander承担的就是把jdbcType和JavaType相互转换的作用。
3,系统自定义typeHandler:
Mybatis自己已经给我们定义好了很多typeHandler了,这里我们截图说明一下:
类型处理器 | Java类型 | JDBC类型 |
BooleanTypeHandler | java.lang.Boolean,boolean | 数据库兼容的 BOOLEAN |
ByteTypeHandler | java.lang.Byte,byte | 数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler | java.lang.Short,short | 数据库兼容的 NUMERIC 或 SHORT INTEGER |
IntegerTypeHandler | java.lang.Integer,int | 数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler | java.lang.Long,long | 数据库兼容的 NUMERIC 或 LONG INTEGER |
FloatTypeHandler | java.lang.Float,float | 数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler | java.lang.Double,double | 数据库兼容的 NUMERIC 或 DOUBLE |
BigDecimalTypeHandler | java.math.BigDecimal | 数据库兼容的 NUMERIC 或 DECIMAL |
StringTypeHandler | java.lang.String | CHAR、VARCHAR |
ClobReaderTypeHandler | java.io.Reader | —— |
ClobTypeHandler | java.lang.String | CLOB、LONGVARCHAR |
NStringTypeHandler | java.lang.String | NVARCHAR、NCHAR |
NClobTypcHandler | java.lang.String | NCLOB |
BlobInputStreamTypeHandler | java.io.InputStream | —— |
ByteArrayTypeHandler | byte[] | 数据库兼容的字节流类型 |
BlobTypeHandler | byte[] | BLOB、LONGVARBINARY |
DateTypeHandler | java.util.Date | TIMESTAMP |
DateOnlyTypeHandler | java.util.Date | DATE |
TimeOnlyTypeHandler | java.util.Date | TIME |
SqlTimestampTypeHandler | java.sql.Timestamp | TIMESTAMP |
SqlDateTypeHandler | java.sql.Date | DATE |
SqlTimeTypeHandler | java.sql.Time | TIME |
ObjectTypeHandler | Any | OTHER或未指定类型 |
EnumTypeHandler | Enumeration Type | VARCHAR 任何兼容的字符串类型,存储枚举的名称(而不是索引) |
EnumOrdinalTypeHandler | Enumeration Type | 任何兼容的 NUMERIC 或 DOUBLE 类型,存储枚举的索引(而不是名称) |
上面就是Mybatis自带的typeHandler。在大部分的情况下无须显式地声明jdbcType javaType,因为MyBatis系统会自己检测。
4,分析typeHandler实现思路
我们这里要自定义类型转换器,我们先参考一下Mybatis自己定义的类型转换器。
这里我们就用最常用StringTypeHandler来举例。我们跟踪了一下代码,我们发现StringTypeHandler继承了BaseTypeHandler,BaseTypeHandler实现了TypeHandler接口。
1,分析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;
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
1,这里有个泛型T,这个泛型就是表示我们的JavaType。比如我们的PO中的某个属性的是String类型,我们就可以把这个T写成String。这里,我们在ResultMap中某个属性上面配置,Mybatis就会自动检查我们对应属性的Java类型,然后设置过来。
2,setParameter方法,使用typeHandler通过PreparedStatement对象进行设置sql参数的时候调用的具体方法。其中i表示Sql参数的下表,parameter是参数,jdbcType表示数据库类型。
3,getResult方法:它的作用就是从jdbc结果集中获取数据进行转换。要么使用列表,要么使用下标。其中最后一个getResult是存储过程用的,我们不说。
2,分析BaseTypeHandler抽象类:
public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {
@Deprecated
protected Configuration configuration;
@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;
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;
}
1,我们在TypeHandler接口里面定义的方法,在本类当中都被实现了,但是,它是通过直接调用对应的抽象方法来实现的。
2,setParameter方法:当parameter和jdbcType同时为空的时候,Mybatis将抛出异常,如果能明确jdbcType,则会进行空设置。如果parameter不为空那么它讲采用setNotNullParameter方法来设置。而setNotNullParameter也是一个抽象方法,我们在自定义的TypeHandler中进行实现。
3,getResult方法:通过调用getNullableResult方法来获取,如果判断为空返回null。getNullableResult也是一个抽象方法,我们也需要在子类中实现。