Mybatis的BaseTypeHandler<T>对于泛型集合List<T>与Json数组反序列化的一种方案, 可支持嵌套泛型类型List<List<T>>等

————————————————

                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:

Link

数据库持久化时有时需要将一些与其他表有级联关系但在查找时较为不便的实体作为数据库字段保存在主表中, 一般会将其转为Json入库, 作为Json保存则有对象和数组两种形式, 如果在反序列化时不指定类型, 则在赋值时会抛出java.lang.ClassCastException异常, 因此对于对象的反序列化还需要获取目标类型

<aside> 👉🏼 数据库持久化时有时需要将一些与其他表有级联关系但在查找时较为不便的实体作为数据库字段保存在主表中, 一般会将其转为Json入库, 作为Json保存则有对象和数组两种形式, 如果在反序列化时不指定类型, 则在赋值时会抛出java.lang.ClassCastException异常, 因此对于对象的反序列化还需要获取目标类型

针对对象类型, 继承BaseTypeHandler<T>后通过含Class<?>的构造方法可以获取原属性的类型, 可以参考Mybatis-plus包中的AbstractJsonTypeHandler<T>

</aside>

————————————————

                        版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/Rhaegal_0411/article/details/134541649


Title

这个解决方案非常巧妙,它通过反射和泛型擦除的结合,动态处理了集合类型的泛型问题。下面是一个更详细的解释和实现:

  1. 通过BaseTypeHandler处理JSON序列化/反序列化
  2. 利用反射机制在运行时获取泛型类型

代码实现

1. 创建 DynamicJsonArrayTypeHandler

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.TableFieldInfo;
import com.baomidou.mybatisplus.core.toolkit.TableInfoHelper;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import com.google.common.collect.Maps;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;

import java.sql.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@MappedTypes(List.class)
public class DynamicJsonArrayTypeHandler extends BaseTypeHandler<List<?>> {

    private static final Map<String, Map<String, Class<?>>> LIST_TYPE_FIELD_GENERIC_METADATA = Maps.newConcurrentMap();

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, List<?> parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter == null ? null : JSONArray.toJSONString(parameter));
    }

    @Override
    public List<?> getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return asList(rs.getMetaData(), rs.findColumn(columnName), rs.getString(columnName));
    }

    @Override
    public List<?> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return asList(rs.getMetaData(), columnIndex, rs.getString(columnIndex));
    }

    @Override
    public List<?> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return asList(cs.getMetaData(), columnIndex, cs.getString(columnIndex));
    }

    private List<?> asList(ResultSetMetaData resultSetMetaData, int columnIndex, String value) throws SQLException {
        return value == null ? null : JSONArray.parseArray(value, findGenericRawType(resultSetMetaData, columnIndex));
    }

    private Class<?> findGenericRawType(ResultSetMetaData resultSetMetaData, int columnIndex) throws SQLException {
        return LIST_TYPE_FIELD_GENERIC_METADATA.computeIfAbsent(
                        resultSetMetaData.getTableName(columnIndex),
                        k -> Optional.ofNullable(TableInfoHelper.getTableInfo(k))
                                .orElseThrow(() -> new RuntimeException("Table metadata not found: " + k))
                                .getFieldList().stream()
                                .filter(f -> List.class.isAssignableFrom(f.getField().getType()))
                                .filter(f -> f.getField().getGenericType() instanceof ParameterizedType)
                                .collect(Collectors.toMap(
                                        TableFieldInfo::getColumn,
                                        f -> Optional.ofNullable(((ParameterizedType) f.getField().getGenericType()).getActualTypeArguments()[0])
                                                .map(t -> t instanceof ParameterizedType ? (Class<?>) ((ParameterizedType) t).getRawType() : (Class<?>) t)
                                                .orElse(Object.class),
                                        (v1, v2) -> v1,
                                        Maps::newConcurrentMap
                                ))
                )
                .getOrDefault(resultSetMetaData.getColumnName(columnIndex), Object.class);
    }
}

2. 使用 DynamicJsonArrayTypeHandler 在实体类中声明

在使用这个通用的 DynamicJsonArrayTypeHandler 时,需要在实体类中指定 autoResultMap = true

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;
import java.util.List;

@TableName(value = "target_table", autoResultMap = true)
public class TargetTable implements Serializable {

    @TableField(typeHandler = DynamicJsonArrayTypeHandler.class)
    private List<TargetType> jsonField;

    // getters and setters
}

解释

  1. DynamicJsonArrayTypeHandler