前情提要 在上一次的需求中,为了实现两个JSON字段的映射,我们自定义了一个AbstractJsonTypeHandler类,然后为每一个实体类定义了各自的TypeHandler。详情可以看上一篇关于TypeHandler的博客:在Mybatis Plus中使用TypeHandler映射PostgreSQL内的JSON字段 。那一个实现虽然能完成需求,但是实在不优雅。对于写代码有强迫症的我,自然需要优化一下方案。
FastjsonTypeHandler 简介 FastjsonTypeHandler 是MybatisPlus提供的默认Handler之一,它是继承了AbstractJsonTypeHandler<Object>
。可以实现各种实体类到JSON字符串的映射。总体的设计思想跟我之前想的很类似,这依旧是使用了模板方法 的设计模式。继承AbstractJsonTypeHandler<Object>
的TypeHandler还有JacksonTypeHandler。
源码解析 我们直接看源码,首先是AbstractJsonTypeHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public abstract class AbstractJsonTypeHandler <T> extends BaseTypeHandler <T> { @Override public void setNonNullParameter (PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, toJson(parameter)); } @Override public T getNullableResult (ResultSet rs, String columnName) throws SQLException { final String json = rs.getString(columnName); return StringUtils.isBlank(json) ? null : parse(json); } @Override public T getNullableResult (ResultSet rs, int columnIndex) throws SQLException { final String json = rs.getString(columnIndex); return StringUtils.isBlank(json) ? null : parse(json); } @Override public T getNullableResult (CallableStatement cs, int columnIndex) throws SQLException { final String json = cs.getString(columnIndex); return StringUtils.isBlank(json) ? null : parse(json); } protected abstract T parse (String json) ; protected abstract String toJson (T obj) ; }
可以看到,它把通用逻辑给定义成了模板方法 ,留出了parse
和toJson
方法供子类实现。
再查看FastjsonTypeHandler 是如何实现的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @MappedTypes({Object.class}) @MappedJdbcTypes({JdbcType.VARCHAR}) public class FastjsonTypeHandler extends AbstractJsonTypeHandler <Object> { private static final Logger log = LoggerFactory.getLogger(FastjsonTypeHandler.class); private final Class<?> type; public FastjsonTypeHandler (Class<?> type) { if (log.isTraceEnabled()) { log.trace("FastjsonTypeHandler(" + type + ")" ); } Assert.notNull(type, "Type argument cannot be null" , new Object [0 ]); this .type = type; } protected Object parse (String json) { return JSON.parseObject(json, this .type); } protected String toJson (Object obj) { return JSON.toJSONString(obj, new SerializerFeature []{SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteNullStringAsEmpty}); } }
我们需要关注的就是 type
变量,可以观察到它由 public FastjsonTypeHandler(Class<?> type)
这个构造方法传入。实例化TypeHandler的操作是在Mybatis内部进行的。以下是对应的源码:
1 2 3 4 5 6 7 8 public <T> TypeHandler<T> getInstance (Class<?> javaTypeClass, Class<?> typeHandlerClass) { if (javaTypeClass != null ) { Constructor<?> c = typeHandlerClass.getConstructor(Class.class); return (TypeHandler<T>) c.newInstance(javaTypeClass); } Constructor<?> c = typeHandlerClass.getConstructor(); return (TypeHandler<T>) c.newInstance(); }
可以看到,当javaTypeClass 被指定时,会通过反射的方式获得参数列表为Class.class 的构造方法,然后传入javaTypeClass进行实例化返回。若为空则调用无参构造方法。
那么FastjsonTypeHandler是如何注册的呢?
Mybatis那些自带的TypeHandler都是通过register
方法注册的,register
方法有大量的重载方法,可以供不同的入参完成注册。它们之间的调用关系可以用下图表示:
但是在MybatisPlus中,它是这样实例化并指定该字段的TypeHandler的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ResultMapping getResultMapping (final Configuration configuration) { ResultMapping.Builder builder = new ResultMapping .Builder(configuration, property, StringUtils.getTargetColumn(column), propertyType); TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry(); if (jdbcType != null && jdbcType != JdbcType.UNDEFINED) { builder.jdbcType(jdbcType); } if (typeHandler != null && typeHandler != UnknownTypeHandler.class) { TypeHandler<?> typeHandler = registry.getMappingTypeHandler(this .typeHandler); if (typeHandler == null ) { typeHandler = registry.getInstance(propertyType, this .typeHandler); } builder.typeHandler(typeHandler); } return builder.build(); }
这段代码的意思是:
第8行 :如果指定了这个变量的typeHandler类型,就进入处理逻辑
第9行 :尝试从已注册的typeHandler的map中获取这个typeHandler类型的实例
第10~11行 :如果这个类型的typeHandler还没被注册,就调用 registry.getInstance(propertyType, this.typeHandler)
。propertyType就是该字段的类型。之后指定实例化的FastjsonTypeHandler作为该字段的typeHandler。
注意:MybatisPlus并不会帮我们注册TypeHandler,而只是实例化+指定。
问题 那么现在的问题就在于,这里的propertyType
是否保留了泛型呢?很遗憾答案是否定的,这里的propertyType
已经仅剩下了List
这样的话它依旧是无法正确的处理集合类型。无奈只能继续使用数组了……。
修改 autoResultMap 要使用FastjsonTypeHandler需要在表实体类上标注 @TableName(value = "problem",autoResultMap = true)
。不然上述的 getResultMapping
方法就不会被调用,也就无法实例化、指定typehandler了。
typeHandler 在对应的字段上添加 @TableField(typeHandler = FastjsonTypeHandler.class)
指定TypeHandler的类型,这也是上述 getResultMapping
方法里 typeHandler = registry.getInstance(propertyType, this.typeHandler);
的this.typeHandler
的入参。
修改后的实体类为:
1 2 3 4 5 6 7 8 9 10 11 12 13 @Data @NoArgsConstructor @AllArgsConstructor @TableName(value = "problem",autoResultMap = true) public class Problem { @TableField(typeHandler = FastjsonTypeHandler.class) private Test[] test; @TableField(typeHandler = FastjsonTypeHandler.class) private Solution[] solution; }
我之前自定的TypeHandler和注册操作的Bean也可以删除了。