mybatis 源码分析六、TypeHandlerRegistry和TypeHandler详解
TypeHandlerRegistry和TypeHandler在mybatis源码type包中,TypeHandler主要的作用是用做JdbcType 和 javaType的类型转换,就是我们常说的数据库类型和java中实体类型的转换。因为类型是非常多的,所以TypeHandler有很多实现类,而TypeHandlerRegistry主要是用于管理这些TypeHandler的。我们可以看一下TypeHandlerRegistry 的源码。
通过源码分析我们可以发现其实TypeHandlerRegistry主要是正对以下几个map进行操作的,
// 通过JDBC类型映射类型处理器
// JdbcType 与TypeHandler 之间的映射关系,JdbcType 是枚举类型,它与 JDBC 规范中的数据类型一一对应
// 该集合的作用是从 ResultSet 中读取数据时,将 JdbcType 转化为 Java 类型
// (由于 JdbcType 数量已知,因此该集合中的 TypeHandler 数据量固定)
private final Map<org.apache.ibatis.type.JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<org.apache.ibatis.type.JdbcType, TypeHandler<?>>(org.apache.ibatis.type.JdbcType.class);
// 通过Java类型和JDBC类型映射唯一的类型处理器
// 从这个map可以看出一个Java类型可以对应多个类型处理器,需要啥可以具体写sql时候进行指定
private final Map<Type, Map<org.apache.ibatis.type.JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<org.apache.ibatis.type.JdbcType, TypeHandler<?>>>();
//未知的类型
private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
// 全部 TypeHandler 的类型与 TypeHandler 实例之间的映射关系(可以认为是缓存)
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
// 空TypeHandler 集合的标志
private static final Map<org.apache.ibatis.type.JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();
private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class
可以看做是正对TypeHandler的缓存,一次性将所有的TypeHandler进行了注册在我们需要使用的时候在重TypeHandlerRegistry注册器中获取。mybatis其实默认替我们注册了很多的TypeHandler。可以看到有很多TypeHandler都已经注册进去了。我们接下来可以看看TypeHandler的作用。
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(org.apache.ibatis.type.JdbcType.BOOLEAN, new BooleanTypeHandler());
register(org.apache.ibatis.type.JdbcType.BIT, new BooleanTypeHandler());
register(Byte.class, new ByteTypeHandler());
register(byte.class, new ByteTypeHandler());
register(org.apache.ibatis.type.JdbcType.TINYINT, new ByteTypeHandler());
register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(org.apache.ibatis.type.JdbcType.SMALLINT, new ShortTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(org.apache.ibatis.type.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(org.apache.ibatis.type.JdbcType.FLOAT, new FloatTypeHandler());
register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(org.apache.ibatis.type.JdbcType.DOUBLE, new DoubleTypeHandler());
register(Reader.class, new ClobReaderTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, org.apache.ibatis.type.JdbcType.CHAR, new StringTypeHandler());
register(String.class, org.apache.ibatis.type.JdbcType.CLOB, new ClobTypeHandler());
register(String.class, org.apache.ibatis.type.JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, org.apache.ibatis.type.JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(String.class, org.apache.ibatis.type.JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, org.apache.ibatis.type.JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, org.apache.ibatis.type.JdbcType.NCLOB, new NClobTypeHandler());
register(org.apache.ibatis.type.JdbcType.CHAR, new StringTypeHandler());
register(org.apache.ibatis.type.JdbcType.VARCHAR, new StringTypeHandler());
register(org.apache.ibatis.type.JdbcType.CLOB, new ClobTypeHandler());
register(org.apache.ibatis.type.JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(org.apache.ibatis.type.JdbcType.NVARCHAR, new NStringTypeHandler());
register(org.apache.ibatis.type.JdbcType.NCHAR, new NStringTypeHandler());
register(org.apache.ibatis.type.JdbcType.NCLOB, new NClobTypeHandler());
register(Object.class, org.apache.ibatis.type.JdbcType.ARRAY, new ArrayTypeHandler());
register(org.apache.ibatis.type.JdbcType.ARRAY, new ArrayTypeHandler());
register(BigInteger.class, new BigIntegerTypeHandler());
register(org.apache.ibatis.type.JdbcType.BIGINT, new LongTypeHandler());
register(BigDecimal.class, new BigDecimalTypeHandler());
register(org.apache.ibatis.type.JdbcType.REAL, new BigDecimalTypeHandler());
register(org.apache.ibatis.type.JdbcType.DECIMAL, new BigDecimalTypeHandler());
register(org.apache.ibatis.type.JdbcType.NUMERIC, new BigDecimalTypeHandler());
register(InputStream.class, new BlobInputStreamTypeHandler());
register(Byte[].class, new ByteObjectArrayTypeHandler());
register(Byte[].class, org.apache.ibatis.type.JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
register(Byte[].class, org.apache.ibatis.type.JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
register(byte[].class, new ByteArrayTypeHandler());
register(byte[].class, org.apache.ibatis.type.JdbcType.BLOB, new BlobTypeHandler());
register(byte[].class, org.apache.ibatis.type.JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(org.apache.ibatis.type.JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(org.apache.ibatis.type.JdbcType.BLOB, new BlobTypeHandler());
register(Object.class, UNKNOWN_TYPE_HANDLER);
register(Object.class, org.apache.ibatis.type.JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(org.apache.ibatis.type.JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(Date.class, new DateTypeHandler());
register(Date.class, org.apache.ibatis.type.JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, org.apache.ibatis.type.JdbcType.TIME, new TimeOnlyTypeHandler());
register(org.apache.ibatis.type.JdbcType.TIMESTAMP, new DateTypeHandler());
register(org.apache.ibatis.type.JdbcType.DATE, new DateOnlyTypeHandler());
register(org.apache.ibatis.type.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());
// mybatis-typehandlers-jsr310
if (Jdk.dateAndTimeApiExists) {
this.register(Instant.class, InstantTypeHandler.class);
this.register(LocalDateTime.class, LocalDateTimeTypeHandler.class);
this.register(LocalDate.class, LocalDateTypeHandler.class);
this.register(LocalTime.class, LocalTimeTypeHandler.class);
this.register(OffsetDateTime.class, OffsetDateTimeTypeHandler.class);
this.register(OffsetTime.class, OffsetTimeTypeHandler.class);
this.register(ZonedDateTime.class, ZonedDateTimeTypeHandler.class);
this.register(Month.class, MonthTypeHandler.class);
this.register(Year.class, YearTypeHandler.class);
this.register(YearMonth.class, YearMonthTypeHandler.class);
this.register(JapaneseDate.class, JapaneseDateTypeHandler.class);
}
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
}
我们看一下TypeHandler的接口中的方法
//设置属性,例如我们使用PreparedStatement 对sql中的参数进行设置时使用
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;
我们看一下其中一个实现类,比如String类的,TypeHandler主要就是通过这3个方法实现参数Jdbc和java类型的装换,大家可以去了解一下原生的Jdbc。
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getString(columnName);
}
@Override
public String getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return rs.getString(columnIndex);
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return cs.getString(columnIndex);
}
除了以上这些TypeHandler,我们自己还可以自定义TypeHandler,处理一下特殊逻辑,例如
- 数据库中存入的字段是json格式我们想要将这个字段转化成实体,我们就可以在这3个方法中进行一下之定义的逻辑操作
- 加密,例如我们数据库中存入了一下加密字段但是客户端传入的不是加密形式,我们在进行数据库查询时就可以通过加密解密的方式设置这个属性的字段值。
接下来我们自定义一个TypeHandler解决将枚举类型转化成描述给前端返回的一个TypeHandler。例如我们现在想将数据库中的Gender性别字段翻译成中文。假设数据中存入的是 1,2这种数组类型。
我们先定义一下我们的枚举类GenderEnum
package org.apache.ibatis.dao.mapper.enums;
public enum GenderEnum {
woman("2","女"),
man("1", "男");
public String code;
public String value;
GenderEnum(String code,String value) {
this.code = code;
this.value = value;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
在定义一个TypeHandler,只需要实现BaseTypeHandler就可以了,
@MappedTypes(value = {GenderEnum.class})
public class EnumTypeHandle extends BaseTypeHandler<GenderEnum> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, GenderEnum parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i,parameter.getCode());
}
@Override
public GenderEnum getNullableResult(ResultSet rs, String columnName) throws SQLException {
return convert(rs.getString(columnName));
}
@Override
public GenderEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return convert(rs.getString(columnIndex));
}
@Override
public GenderEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return convert(cs.getString(columnIndex));
}
private GenderEnum convert(String string) {
GenderEnum[] values = GenderEnum.values();
for (GenderEnum value : values) {
if (value.getCode().equals(string)) {
return value;
}
}
return null;
}
}
我们现在只需要将我们自定义的TypeHandler注入到mybatis中,就可以使用了
<typeHandlers>
<typeHandler handler="org.apache.ibatis.dao.mapper.typeHandle.EnumTypeHandle" javaType="org.apache.ibatis.dao.mapper.enums.GenderEnum"/>
</typeHandlers>
这样我们将我们自定义的TypeHandler注入到了我们的项目中。我们可以看一下执行的结果。可以看到我们的GenderEnum字段属性已经被赋值了。
Admin{uid='1723ad77a1ca06b29a011471036a2dd6', userName='test1', passWord='$2a$10$eus6faJ6/X2hNo5Cm.7bK.Z0oP9UQADthoqRaGTwHEdwehnaMj4Lu', gender='man', email='1595833114@qq.com', mobile='null', validCode='null', summary='null', loginCount=47, lastLoginIp='0:0:0:0:0:0:0:1', status='0', createTime=Sun Mar 22 23:23:12 CST 2020, updateTime=Tue Mar 23 09:55:33 CST 2021, nickName='test1', occupation='null', github='null', gitee='null', roleUid='d105da79260f4d6a8a03571e4a2b17bc', personResume='null'}