前言
Java对象和数据库表字段之间的映射,Mybatis内置了一系列默认的处理类,比如LongTypeHandler、DataTypeHandler等,绝大多数情况下都可以让开发人员直接读写数据,不需要自定义。但万事不绝对,总有一些比较特殊的场景,给开发人员带来一些困扰和挑战。这一章我们就来看看在Mybatis中,如何用自定义类型来处理Json类型的数据。
配置
自定义的TypeHandler,需要在配置文件中指定package,方便初始化时,读取package里面所有的自定义TypeHandler,并将其注册到Mybatis上下文。
只需要下面这一行配置即可,后面会介绍其使用环节。
mybatis.type-handlers-package: com.essay.mybatis.plugin
序列图
注册的序列图
使用自定义TypeHandler
关键源码
涉及的类不多,就不画序列图了。关键源码分为两大块,注册自定义TypeHandler和使用。
注册
在TypeHandlerRegistry类中完成注册
public void register(Class<?> typeHandlerClass) {
boolean mappedTypeFound = false;
// 读取@MappedTypes注解
MappedTypes mappedTypes = typeHandlerClass.getAnnotation(MappedTypes.class);
if (mappedTypes != null) {
for (Class<?> javaTypeClass : mappedTypes.value()) {
// 从@MappedTypes注解中获取javaType,并完成后续注册
register(javaTypeClass, typeHandlerClass);
mappedTypeFound = true;
}
}
if (!mappedTypeFound) {
// 如果没有配置@MappedTypes,则注册为全类型处理器
register(getInstance(null, typeHandlerClass));
}
}
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);
typeHandlerMap.put(javaType, map);
}
allTypeHandlersMap.put(handler.getClass(), handler);
}
几个关键步骤:
- 读取mybatis.type-handlers-package配置目录下的自定义类型处理类;
- 遍历
- 读取自定义类型处理类上的@MappedTypes注解,获取映射的javaType;
- 最后将映射关系存储到内部集合:typeHandlerMap;
- 如果没有配置@MappedTypes,找不到javaType,则注册为全类型处理器,放入到allTypeHandlersMap集合;
使用typeHandler
在DefaultResultHandler中,直接从ResultMapping中获取对应的typeHandler,对数据库字段进行转换。
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
// 获取typeHandler
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
// 用typeHandler转换数据库字段为对应的java类型
return typeHandler.getResult(rs, column);
}
}
注意这一行:
typeHandler.getResult(rs, column);在自定义的类型处理器中,主要就是实现了getResult方法。
举例
表对象
这里需要自定义转换的属性是licenseInfo,在表对象中,不需要任何处理或者注解。
@Table(name = "tb_image")
@Data
public class ImageInfo implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
/**
* 图片摘要信息
*/
private String md5;
// 忽略展示其余字段.................
/**
* 图片授权信息
*/
private ImageLicense licenseInfo;
}
自定义TypeHandler
这里我们将数据库中的json字符串,转换为ImageLicense对象。
/**
* 类型转换,json字符串转换为ImageLicense对象
*
* @author yy
*/
@MappedJdbcTypes(JdbcType.VARCHAR)
@MappedTypes(ImageLicense.class)
public class LicenseJsonTypeHandler implements TypeHandler<ImageLicense> {
@Override
public void setParameter(PreparedStatement ps, int i,
ImageLicense parameter, JdbcType jdbcType) throws SQLException {
// 查询参数装配
ps.setString(i, JSON.toJSONString(parameter));
}
@Override
public ImageLicense getResult(ResultSet rs, String columnName) throws SQLException {
// 读取字段数据
String json = rs.getString(columnName);
// 转换为java对象
return JSON.parseObject(json, ImageLicense.class);
}
@Override
public ImageLicense getResult(ResultSet rs, int columnIndex) throws SQLException {
// 读取字段数据
String json = rs.getString(columnIndex);
// 转换为java对象
return JSON.parseObject(json, ImageLicense.class);
}
@Override
public ImageLicense getResult(CallableStatement cs, int columnIndex) throws SQLException {
// 读取字段数据
String json = cs.getString(columnIndex);
// 转换为java对象
return JSON.parseObject(json, ImageLicense.class);
}
}
在Mapper中使用
这里只考虑以注解的方式来使用,下面两种方式,在@Result注解中配置,可以独立使用,也可以合并使用,都会产生预期效果。
@Result.javaType
@Select("select * from tb_image where md5 = #{md5}")
@Result(
property = "licenseInfo",
column = "license_info",
javaType = ImageLicense.class)
ImageInfo byMd5(@Param(value = "md5") String md5);
单独使用javaType,会根据配置的@MappedTypes(ImageLicense.class)去找到适配的typeHandler。
捋一下相关逻辑:
在创建ResultMapping时,如果typeHandler没有配置,但是配置了javaType,可以根据typeHandlerMap映射关系,获取到对应的typeHandler,并存储到ResultMapping.typeHandler属性中。
private void resolveTypeHandler() {
if (resultMapping.typeHandler == null && resultMapping.javaType != null) {
Configuration configuration = resultMapping.configuration;
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
// 从typeHandler注册类中,根据javaType获取相应的typeHandler
resultMapping.typeHandler = typeHandlerRegistry.getTypeHandler(resultMapping.javaType, resultMapping.jdbcType);
}
}
@Result.typeHandler
@Select("select * from tb_image where md5 = #{md5}")
@Result(
property = "licenseInfo",
column = "license_info",
typeHandler = LicenseJsonTypeHandler.class)
ImageInfo byMd5(@Param(value = "md5") String md5);
如果配置了typeHandler,就没有上面那么多麻烦,在DefaultResultHandler中,直接从ResultMapping中获取对应的typeHandler,对数据库字段进行转换。