You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MyBatis如何使用泛型JsonTypeHandler按type值将JSON字段映射为不同对象

问题根因

  • 你当前自定义的JsonTypeHandler仅提供带Class<T>参数的构造方法,MyBatis默认调用无参构造实例化TypeHandler,导致泛型实际类型丢失,Jackson反序列化时没有指定目标类,默认返回Map类型
  • 你需要根据type字段动态匹配不同的反序列化目标类,固定配置的BaseResultMap无法实现动态类型绑定

修复方案

步骤1:调整JsonTypeHandler

补充无参构造,兼容MyBatis的实例化逻辑:

public class JsonTypeHandler <T> extends BaseTypeHandler<T> {
    private static final Logger LOGGER = LoggerFactory.getLogger(JsonTypeHandler.class);
    private static final ObjectMapper mapper = new ObjectMapper();
    private Class<T> clazz;

    static {
        mapper.configure(JsonParser.Feature.ALLOW_MISSING_VALUES, false);
        mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }

    // 新增无参构造,供MyBatis反射实例化时使用
    public JsonTypeHandler() {
        // 获取泛型实际类型
        Type superClass = getClass().getGenericSuperclass();
        if (superClass instanceof ParameterizedType) {
            this.clazz = (Class<T>) ((ParameterizedType) superClass).getActualTypeArguments()[0];
        }
    }

    // 保留原有带Class参数的构造
    public JsonTypeHandler(Class<T> clazz) {
        if (clazz == null) {
            throw new NullPointerException("Type argument cannot be null");
        }
        this.clazz = clazz;
    }

    // 原有toJSON、toObject、参数设置、结果获取方法保持不变,直接复用你现有代码即可
    private String toJSON(T object) {
        try {
            String string = mapper.writeValueAsString(object);
            LOGGER.info(">>>> json handler string:{} <<<<",string);
            return string;
        } catch (Exception e) {
            LOGGER.error(">>>> covert object to json string failed, error message: <<<<",e);
        }
        return null;
    }

    private T toObject(String json, Class<T> clazz) throws IOException {
        if (json != null && !json.isEmpty()) {
            return mapper.readValue(json,clazz);
        }
        return null;
    }

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, T t, JdbcType jdbcType) throws SQLException {
        try {
            preparedStatement.setString(i,toJSON(t));
        } catch (Exception e) {
            LOGGER.error(">>>> preparedStatement set string failed, error message:{} <<<<",e.getMessage());
        }
    }

    @Override
    public T getNullableResult(ResultSet resultSet, String s) throws SQLException {
        try {
            return toObject(resultSet.getString(s), clazz);
        } catch (IOException e) {
            LOGGER.error(">>>> convert json string to object failed, error message:{} <<<<",e.getMessage());
        }
        return null;
    }

    @Override
    public T getNullableResult(ResultSet resultSet, int i) throws SQLException {
        try {
            return toObject(resultSet.getString(i), clazz);
        } catch (IOException e) {
            LOGGER.error(">>>> convert json string to object failed, error message:{} <<<<",e.getMessage());
        }
        return null;
    }

    @Override
    public T getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        try {
            return toObject(callableStatement.getString(i), clazz);
        } catch (IOException e) {
            LOGGER.error(">>>> convert json string to object failed, error message:{} <<<<",e.getMessage());
        }
        return null;
    }
}

步骤2:修改Mapper ResultMap配置,添加鉴别器

用MyBatis的discriminator鉴别器根据type字段值,动态绑定不同的反序列化类型:

<mapper namespace="com.example.demo.dao.mapper.TblTestJsonMapper">
    <resultMap id="BaseResultMap" type="com.example.demo.dao.po.TestJsonPO">
        <id property="id" column="id" jdbcType="BIGINT"/>
        <result property="type" column="type" jdbcType="INTEGER"/>
        <!-- 配置鉴别器,根据type值匹配不同的resultMap -->
        <discriminator javaType="int" column="type">
            <case value="1" resultMap="Type1ResultMap"/>
            <case value="2" resultMap="Type2ResultMap"/>
        </discriminator>
    </resultMap>

    <!-- type=1时的结果映射,指定extInfo反序列化为TestJsonExtInfoPO -->
    <resultMap id="Type1ResultMap" type="com.example.demo.dao.po.TestJsonPO" extends="BaseResultMap">
        <result property="extInfo" column="ext_info" 
                javaType="com.example.demo.dao.po.TestJsonExtInfoPO"
                jdbcType="VARCHAR"
                typeHandler="com.example.demo.JsonTypeHandler"/>
    </resultMap>

    <!-- type=2时的结果映射,指定extInfo反序列化为TestJsonExtInfoPO2 -->
    <resultMap id="Type2ResultMap" type="com.example.demo.dao.po.TestJsonPO" extends="BaseResultMap">
        <result property="extInfo" column="ext_info" 
                javaType="com.example.demo.dao.po.TestJsonExtInfoPO2"
                jdbcType="VARCHAR"
                typeHandler="com.example.demo.JsonTypeHandler"/>
    </resultMap>

    <!-- 原有sql、insert、select配置保持不变 -->
    <sql id="allCols">
        `id` ,`type`,ext_info
    </sql>
    
    <insert id="insert" parameterType="com.example.demo.dao.po.TestJsonPO">
        INSERT INTO tbl_test_json (`type`, ext_info)
        values (#{type},
                #{extInfo,typeHandler=com.example.demo.JsonTypeHandler})
    </insert>
    
    <select id="selectByType" resultMap="BaseResultMap">
        select
        <include refid="allCols"/>
        from tbl_test_json
        where `type` = #{type}
        limit 1
    </select>
</mapper>

步骤3:测试验证

你的原有测试代码无需修改,查询结果的extInfo就会自动匹配对应类型:

@Resource
private TblTestJsonMapper<TestJsonExtInfoPO> tblTestJsonMapper1;
@Resource
private TblTestJsonMapper<TestJsonExtInfoPO2> tblTestJsonMapper2;
    
public void test() {
    // type=1时extInfo为TestJsonExtInfoPO类型
    TestJsonPO<TestJsonExtInfoPO> result1 = tblTestJsonMapper1.selectByType(1);
    // type=2时extInfo为TestJsonExtInfoPO2类型
    TestJsonPO<TestJsonExtInfoPO2> result2 = tblTestJsonMapper2.selectByType(2);
}

内容的提问来源于stack exchange,提问作者HongYi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 06:57:03