水煮MyBatis(十九)- 自定义类型处理器

257 阅读3分钟

前言

Java对象和数据库表字段之间的映射,Mybatis内置了一系列默认的处理类,比如LongTypeHandler、DataTypeHandler等,绝大多数情况下都可以让开发人员直接读写数据,不需要自定义。但万事不绝对,总有一些比较特殊的场景,给开发人员带来一些困扰和挑战。这一章我们就来看看在Mybatis中,如何用自定义类型来处理Json类型的数据。

配置

自定义的TypeHandler,需要在配置文件中指定package,方便初始化时,读取package里面所有的自定义TypeHandler,并将其注册到Mybatis上下文。
只需要下面这一行配置即可,后面会介绍其使用环节。

mybatis.type-handlers-package: com.essay.mybatis.plugin

序列图

注册的序列图

image.png

使用自定义TypeHandler

image.png

关键源码

涉及的类不多,就不画序列图了。关键源码分为两大块,注册自定义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,对数据库字段进行转换。

调用

image.png