需求背景:将表的某字段类型改成列表。
因映射关系变动,导致字段仍需以字符串类型进行存储。如javatype仍为String,代码层面需要进行适配,一方面需要修改的代码块大大增加,另一方面会大大降低代码语义,影响阅读质量。
MybatisPlus的字段处理器能够很好完成JavaType和jdbc的转换。
问题引出:
数据示例:
针对多个算法进行提测时,算法id列表以逗号分隔形式的字符串进行保留
@TableName(value = "t_image_test")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ImageTest extends Model<ImageTest> {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 算法ID
* 在多算法模式提测下,这里可能有多个
*/
@TableField(typeHandler = ListInt2StringHandler.class)
private List<Integer> algoId;
/**
* 算法版本
*/
@TableField(typeHandler = ListStr2StringHandler.class)
private List<String> algoVersion;
// ***省略
}
字段处理器
public class ListInt2StringHandler extends BaseTypeHandler<List<Integer>> {
private static final String DELIM = ",";
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, List<Integer> strings, JdbcType jdbcType) throws SQLException {
String value = StringUtils.collectionToDelimitedString(strings, DELIM);
preparedStatement.setString(i, value);
}
@Override
public List<Integer> getNullableResult(ResultSet resultSet, String s) throws SQLException {
String value = resultSet.getString(s);
return split2List(value);
}
@Override
public List<Integer> getNullableResult(ResultSet resultSet, int i) throws SQLException {
String value = resultSet.getString(i);
return split2List(value);
}
@Override
public List<Integer> getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
String value = callableStatement.getString(i);
return split2List(value);
}
private List<Integer> split2List(String value) {
if (StrUtil.isEmpty(value)) {
return null;
}
String[] split = value.split(DELIM);
return StreamUtil.of(split).mapToInt(Integer::parseInt).boxed().collect(Collectors.toList());
}
}
测试代码如下:
@PostMapping("/test")
public R<ImageTest> test() {
return R.ok(imageTestService.getById(2004));
}
控制台输出:
Caused by: java.lang.NumberFormatException: For input string: "cas_v1.1.0_1"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
/*~~~*/
at com.ev.amp.config.mybatisplus.handler.ListInt2StringHandler.split2List(ListInt2StringHandler.java:55)
由控制台输出日志可知是algoVersion
字段引用了错误的处理器导致的类型转换异常。
问题分析
mybatis为什么会选择错了处理器?mybatis又是如何选择处理器的?
接下来根据线程调用栈一步步分析,mybatis到底是如何选择处理器的
mybatis通过反射获取当前字段类型,再通过字段类型从处理器注册器中获取对应的字段处理器(注册器就是一个map,key为字段类型,value为具体的处理器)。
那处理器是如何进行注册的?mybatis进行加载上下文配置时,扫描所有Handler并注册进来。由于List和List类类型相同(泛型擦除),后面加载的处理器将之前的覆盖掉了,导致使用该类型的所有的字段都使用了该处理器。
解决方案
上述使用自动映射(resultType)就必然选择不了正确的处理器,可以试试使用手动映射方式(使用resultMap)
1.实体类添加autoResultMap
mapper.xml文件添加对应处理器
<resultMap id="BaseResultMap" type="com.ev.amp.plant.infra.po.ImageTest">
<id column="id" property="id" />
<result column="name" property="name" />
<result column="algo_id" property="algoId" typeHandler="com.ev.amp.config.mybatisplus.handler.ListInt2StringHandler"/>
<result column="sdk_id" property="sdkId" />
<result column="algo_server_id" property="algoServerId" />
<result column="algo_version" property="algoVersion" typeHandler="com.ev.amp.config.mybatisplus.handler.ListStr2StringHandler" />
</resultMap>
控制台输出
另一方面,我们可以创建一个子类继承集合List,不同字段使用不同集合类处理也可以解决如上问题。