自定义typehandler配合mybatis,实现自动json化存储数据库,查询时自动解析为正确类型
做项目时,有一些业务数据,需要存储到数据库,它的类型是数组,即一个对象对应多条数据,使用关联表太麻烦了,而且维护起来也不直观,于是就像能不能直接将List,json化之后把json字符串存到数据库,然后查询的时候,再自动映射回来。以此类推,对于更多层List嵌套,或者转换时,无法识别的情况,需要自己新增一个对应的TypeHandler,指定继承类型即可,使用该特定TypeHandler。例如:Li
需求背景:
做项目时,有一些业务数据,需要存储到数据库,它的类型是数组,即一个对象对应多条数据,使用关联表太麻烦了,而且维护起来也不直观,于是就像能不能直接将List,json化之后把json字符串存到数据库,然后查询的时候,再自动映射回来
例如:List<String> → ["PAUSE", "danger", "Lock"]
由于我有很多种类型的数据都打算使用这个方式存储,期间花费大量的时间经过大量的尝试,发现在parse的过程中,typeHandler是无法自动获取目标类型的,对于自定义类型和特殊情况,需要针对性处理。
自定义一个JsonTypeHandler继承AbstractJsonTypeHandler:
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonTypeHandler<T> extends AbstractJsonTypeHandler<T> {
private static final ObjectMapper objectMapper = new ObjectMapper();
private TypeReference<T> typeReference;
// 无参构造函数
public JsonTypeHandler() {
// 默认构造函数,初始化为一个通用的类型
this.typeReference = new TypeReference<T>() {};
}
// 带参构造函数,接受 TypeReference
public JsonTypeHandler(TypeReference<T> typeReference) {
this.typeReference = typeReference;
}
@Override
protected T parse(String json) {
try {
if (typeReference == null) {
throw new IllegalStateException("TypeReference is not set. Please provide a TypeReference.");
}
return objectMapper.readValue(json, typeReference);
} catch (Exception e) {
throw new RuntimeException("Failed to parse JSON", e);
}
}
@Override
protected String toJson(T obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException("Failed to convert to JSON", e);
}
}
}
对应的实体类使用,注解中需要指定typeHandler :
@TableField(value = "school_list", typeHandler = JsonTypeHandler.class)
private List<String> schoolList;
该实体类的头部注解指定表名时,还需要指定 autoResultMap = true:
@TableName(value = "t_my_table", autoResultMap = true)
public class MyClass
使用时,遇到第一个问题:
由于我的数据库主键是Long类型,这是考虑到后期数据增长,Integer肯定存不下,于是使用了Long,但是前期由于数据量并不大,主键的值很小,所以在反序列化时,将类似:
[123,456] 的数据,反序列化为了List<Integer>类型,这自然和实体中定义的List<Long>对应不上,所以报错了。
解决方案
需要另外单独写一个自定义typeHandler来继承该JsonTypeHandler,并在定义时就指定类型:
import com.fasterxml.jackson.core.type.TypeReference;
//继承时就指定了类型
public class LongListTypeHandler extends JsonTypeHandler<List<Long>> {
// 无参构造函数,用于 MyBatis 实例化
public LongListTypeHandler() {
super(new TypeReference<>() {});
}
}
然后,对应使用的地方,由 JsonTypeHandler 改为新的 LongListTypeHandler:
@TableField(value = "team_member_id_list", typeHandler = LongListTypeHandler.class)
private List<Long> teamMemberIdList;
第二个问题,对于自定义Object类型无法转换
这是我自定义的一个类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ReceiveInfo {
private String alipayCode;
private String wechatCode;
private String alipayQrCode;
private String wechatQrCode;
}
运行也报错了
解决方案
也需要新增一个针对性的typeHandler:
public class ReceiveInfoTypeHandler extends JsonTypeHandler<ReceiveInfo> {
public ReceiveInfoTypeHandler() {
super(new TypeReference<>() {
});
}
}
以此类推,对于更多层List嵌套,或者转换时,无法识别的情况,需要自己新增一个对应的TypeHandler,指定继承类型即可,使用该特定TypeHandler。
再例如:
@TableField(value = "boss_list", typeHandler = MyClassListTypeHandler.class)
private List<List<MyClass>> bossList = new ArrayList<>();
这里的复杂类型: List<List<BossPlayerDTO>> bossList ,需要自定义typeHandler:
public class MyClassListTypeHandler extends JsonTypeHandler<List<List<MyClass>>> {
public MyClassListTypeHandler() {
super(new TypeReference<>() {
});
}
}
到这里,如果你的查询是通过mybatis-plus自带的list,page,queryWrapper等方式查询,已经能够正常使用了,但是碰到复杂查询,需要自己在mapper.xml中写的SQL进行查询,那还需要对结果集中的字段指定typeHandler进行处理,否则查询到的该字段会为空。
例如:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.codawave.pve.mapper.TeamScheduleMapper">
<resultMap id="TeamSchedulerResultMap" type="com.codawave.pve.model.entity.pve.TeamSchedule">
<!-- ID字段 -->
<id column="id" property="id"/>
<!-- 仅对需要自定义TypeHandler的字段进行明确配置 -->
<result column="scheduled_player_id_list" property="scheduledPlayerIdList"
typeHandler="com.codawave.pve.handlers.custom.LongListTypeHandler"
jdbcType="VARCHAR"/>
<!-- enum类型字段,没有可不写 -->
<result column="status" property="status" javaType="com.codawave.pve.enums.TeamScheduleStatus"/>
</resultMap>
<select id="getDefaultTeamScheduler" resultMap="TeamSchedulerResultMap">
SELECT *
FROM pve_team_schedule
WHERE team_id = #{teamId}
ORDER BY CASE
WHEN status = 'WAITING' THEN 1
WHEN status = 'LOCKED' THEN 2
WHEN status = 'SETTLED' THEN 3
ELSE 4
END,
preset_time DESC
LIMIT 1
</select>
</mapper>
如果对你有帮助,还请点赞支持!
如果使用过程中遇到问题,欢迎评论提出,可以帮助解决。
更多推荐
所有评论(0)