Mybatis的BaseTypeHandler<T>对于泛型集合List<T>与Json数组反序列化的一种方案, 可支持嵌套泛型类型List<List<T>>等
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:
数据库持久化时有时需要将一些与其他表有级联关系但在查找时较为不便的实体作为数据库字段保存在主表中, 一般会将其转为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
这个解决方案非常巧妙,它通过反射和泛型擦除的结合,动态处理了集合类型的泛型问题。下面是一个更详细的解释和实现:
BaseTypeHandler
处理JSON序列化/反序列化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);
}
}
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
}
BaseTypeHandler
是 MyBatis 提供的一个扩展点,允许我们自定义参数设置和结果集提取的行为。LIST_TYPE_FIELD_GENERIC_METADATA
存储了每个表的字段与其泛型类型的映射关系。setNonNullParameter
方法将对象序列化为 JSON 字符串存储到数据库。getNullableResult
方法将 JSON 字符串反序列化为相应的对象。asList
方法根据字段的元数据和 JSON 字符串,解析 JSON 数组为指定的泛型类型。findGenericRawType
方法通过反射机制获取泛型类型,避免了泛型擦除的问题。