找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3161

积分

0

好友

439

主题
发表于 12 小时前 | 查看: 2| 回复: 0

在使用 MyBatis 进行数据库操作时,处理 JSON 类型字段是常见需求。传统做法是为每一种需要序列化或反序列化的对象类型单独编写一个 TypeHandler,就像下面这样:

// 传统做法:每个类型都要写一个 TypeHandler
public class UserInfoTypeHandler extends BaseTypeHandler<UserInfo> { ... }
public class OrderDetailTypeHandler extends BaseTypeHandler<OrderDetail> { ... }
public class ConfigTypeHandler extends BaseTypeHandler<Config> { ... }
// ... 还有更多

这种方式带来了几个显而易见的痛点:

  • 代码重复:每个 TypeHandler 的核心逻辑几乎一模一样,都是序列化和反序列化。
  • 维护成本高:项目里每新增一个需要存储为 JSON 的对象类型,你就得跟着新增一个对应的 TypeHandler。
  • 泛型支持差:对于 List<UserInfo>Map<String, Object> 这类包含泛型的复杂类型,传统的 TypeHandler 很难优雅地处理,往往会丢失泛型信息。

本文将深入探讨如何实现一个通用的 JSON TypeHandler。你只需要这一个类,就能应对所有 JSON 字段的转换需求,并且它能完美支持各种泛型场景,从根本上解决上述问题。

MyBatis TypeHandler 基础

在开始动手实现之前,我们先来回顾一下 MyBatis 中 TypeHandler 的基本工作原理。

TypeHandler 的核心作用

TypeHandler 是 MyBatis 框架中负责在 Java 类型和 JDBC 类型之间进行双向转换的桥梁。它继承自 BaseTypeHandler<T>,并需要实现以下几个核心方法:

public abstract class BaseTypeHandler<T> implements TypeHandler<T> {
    // 将 Java 对象转换为 JDBC 参数(存入数据库)
    public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType);

    // 从 ResultSet 中获取结果(通过列名)
    public abstract T getNullableResult(ResultSet rs, String columnName);

    // 从 ResultSet 中获取结果(通过列索引)
    public abstract T getNullableResult(ResultSet rs, int columnIndex);

    // 从 CallableStatement 中获取结果
    public abstract T getNullableResult(CallableStatement cs, int columnIndex);
}

传统实现方式的局限

常见的传统实现是在 TypeHandler 的构造函数中传入具体的类型信息:

public class UserInfoTypeHandler extends BaseTypeHandler<UserInfo> {
    private Class<UserInfo> type;

    public UserInfoTypeHandler(Class<UserInfo> type) {
        this.type = type;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, UserInfo parameter, JdbcType jdbcType) {
        // 序列化逻辑
    }

    @Override
    public UserInfo getNullableResult(ResultSet rs, String columnName) {
        // 反序列化逻辑
    }
}

这种方式存在两个根本性问题:

  1. 类型信息在构造时固化:MyBatis 在初始化 TypeHandler 实例时,通常无法获取到字段完整的类型信息,尤其是泛型参数。
  2. 缺乏动态性:一个 TypeHandler 实例被创建后,就只能处理它构造函数中指定的那一种类型,无法根据运行时查询的字段动态识别类型。

通用 TypeHandler 的设计思路

要实现一个真正通用的 TypeHandler,核心思路必须是:在运行时动态地获取字段的实际类型信息。我们不能再依赖构造时传入的类型。

核心设计要点

  1. 延迟类型识别:把类型识别的时机从构造函数推迟到 getNullableResult 方法被执行时。
  2. 利用数据库元数据:通过 ResultSetMetaData 可以获取到当前结果集的表名和列名。
  3. 获取完整类型(包括泛型):这是最关键的一步。我们需要根据表名和列名,找到对应的实体类(Entity)的字段(Field),然后调用 Field.getGenericType() 方法来获取该字段的完整类型信息(包含泛型参数)。

核心流程

整个动态识别的流程可以概括为以下链条:

查询结果 → ResultSetMetaData → 表名 + 列名 → 实体类元数据 → Field对象 → getGenericType() → 完整类型信息(含泛型)

关键点:使用 Field.getType() 只能得到原始类型(如 List.class),会丢失泛型信息。而 Field.getGenericType() 可以返回一个 Type 对象,它能完整描述像 List<OrderDetail> 这样的参数化类型。

核心实现原理

1. 获取表名和列名

getNullableResult 方法中,我们可以通过 ResultSetMetaData 来获取当前列的元数据信息:

ResultSetMetaData metaData = rs.getMetaData();
String tableName = metaData.getTableName(columnIndex);  // 获取表名
String columnName = metaData.getColumnName(columnIndex); // 获取列名

2. 查找实体类字段并获取类型

拿到表名和列名后,下一步就是找到对应的实体类字段,并获取其完整的类型信息。这里有几种方案。

方案一:利用 MyBatis-Plus 的 TableInfo(推荐)

MyBatis-Plus 提供了 TableInfoHelper 来管理和缓存实体类的元数据,这让我们的工作变得非常简单:

// 通过表名获取 TableInfo
TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);

// 通过数据库列名查找字段信息(注意:这里用的是数据库列名,不是Java属性名)
TableFieldInfo fieldInfo = tableInfo.getFieldList().stream()
    .filter(f -> columnName.equalsIgnoreCase(f.getColumn()))
    .findFirst()
    .orElse(null);

if (fieldInfo != null) {
    // 获取字段对象
    Field field = fieldInfo.getField();

    // 关键:使用 getGenericType() 获取完整类型信息(包括泛型)
    Type fieldType = field.getGenericType();

    // fieldType 可能是:
    // - Class<?>:简单类型,如 UserInfo.class
    // - ParameterizedType:泛型类型,如 List<UserInfo>、Map<String, Object>
    // - GenericArrayType:泛型数组,如 T[]
    // - TypeVariable:类型变量,如 T
    // - WildcardType:通配符类型,如 ? extends Number
}

方案二:自行维护实体类元数据(不依赖 MyBatis-Plus)

如果你没有使用 MyBatis-Plus,也可以自己实现一个简单的元数据管理器:

// 自定义实体类元数据管理器
public class EntityMetadataManager {
    private static final Map<String, Class<?>> tableToEntityMap = new ConcurrentHashMap<>();
    private static final Map<Class<?>, Map<String, Field>> entityFieldMap = new ConcurrentHashMap<>();

    // 注册实体类
    public static void registerEntity(Class<?> entityClass, String tableName) {
        tableToEntityMap.put(tableName.toLowerCase(), entityClass);

        // 缓存字段信息(考虑 @Column 注解)
        Map<String, Field> fieldMap = new HashMap<>();
        for (Field field : getAllFields(entityClass)) {
            String columnName = getColumnName(field); // 从 @Column 注解或字段名获取
            fieldMap.put(columnName.toLowerCase(), field);
        }
        entityFieldMap.put(entityClass, fieldMap);
    }

    /**
     * 获取字段的完整类型信息(包括泛型)
     * 这是核心方法:通过 getGenericType() 获取类型
     */
    public static Type getFieldType(String tableName, String columnName) {
        Class<?> entityClass = tableToEntityMap.get(tableName.toLowerCase());
        if (entityClass == null) return null;

        Map<String, Field> fieldMap = entityFieldMap.get(entityClass);
        if (fieldMap == null) return null;

        Field field = fieldMap.get(columnName.toLowerCase());
        if (field == null) return null;

        // 关键:使用 getGenericType() 而不是 getType()
        // getType() 只能获取原始类型,会丢失泛型信息
        // getGenericType() 可以获取完整类型,包括泛型参数
        return field.getGenericType();
    }

    /**
     * 获取所有字段(包括父类)
     */
    private static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        while (clazz != null && clazz != Object.class) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            clazz = clazz.getSuperclass();
        }
        return fields;
    }
}

关键区分:getGenericType() vs getType()

让我们通过一个例子看清两者的区别:

public class User {
    private UserInfo userInfo;                    // 简单类型
    private List<OrderDetail> orderDetails;        // 泛型类型
    private Map<String, Object> config;            // 复杂泛型类型
}

// 对于字段 orderDetails
Field field = User.class.getDeclaredField("orderDetails");

// ❌ 错误:使用 getType() 会丢失泛型信息
Class<?> rawType = field.getType();
// 结果:rawType = List.class(丢失了 OrderDetail 信息)

// ✅ 正确:使用 getGenericType() 保留完整类型信息
Type genericType = field.getGenericType();
// 结果:genericType = ParameterizedType(List<OrderDetail>)
// 可以通过 ParameterizedType.getActualTypeArguments() 获取泛型参数

3. 深入理解 Java 的 Type 体系

为了正确处理泛型,我们需要对 Java 的 Type 接口及其子类有所了解:

public interface Type {
    String getTypeName();
}

// Type 的主要实现类:
// 1. Class - 原始类型
// 2. ParameterizedType - 参数化类型(泛型类型)
// 3. GenericArrayType - 泛型数组类型
// 4. TypeVariable - 类型变量
// 5. WildcardType - 通配符类型

处理最常见的 ParameterizedType

ParameterizedType 是我们在处理 List<T>Map<K,V> 时最常遇到的:

private Type getFieldType(String tableName, String columnName) {
    // ... 获取 Field 对象 ...
    Field field = getField(tableName, columnName);
    Type genericType = field.getGenericType();

    if (genericType instanceof ParameterizedType) {
        ParameterizedType parameterizedType = (ParameterizedType) genericType;

        // 获取原始类型(如 List、Map)
        Type rawType = parameterizedType.getRawType();
        // rawType = List.class 或 Map.class

        // 获取泛型参数(如 List<String> 中的 String)
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        // 对于 List<String>:actualTypeArguments = [String.class]
        // 对于 Map<String, UserInfo>:actualTypeArguments = [String.class, UserInfo.class]

        // 获取所有者类型(对于内部类)
        Type ownerType = parameterizedType.getOwnerType();

        return parameterizedType;
    }

    return genericType;
}

如何使用

1. 在实体类中使用

在实体类的字段上通过 @TableField 注解指定我们的通用处理器即可:

@TableName("t_user")
public class User {
    @TableField(typeHandler = CommonJsonTypeHandler.class)
    private UserInfo userInfo;  // 简单对象

    @TableField(typeHandler = CommonJsonTypeHandler.class)
    private List<OrderDetail> orderDetails;  // 支持泛型

    @TableField(typeHandler = CommonJsonTypeHandler.class)
    private Map<String, Object> config;  // 支持 Map
}

2. 在 MyBatis XML 映射文件中使用

你也可以在 XML 的 <result> 标签中直接指定 typeHandler:

<resultMap id="userResultMap" type="User">
    <result column="user_info" property="userInfo"
            typeHandler="com.mmyf.commons.mybatis.CommonJsonTypeHandler"/>
    <result column="order_details" property="orderDetails"
            typeHandler="com.mmyf.commons.mybatis.CommonJsonTypeHandler"/>
</resultMap>

这样一来,无论是简单的 POJO,还是复杂的 List<T>Map<K,V> 甚至 List<List<String>>,都可以用这同一个 CommonJsonTypeHandler 来处理,极大地提升了代码的复用性和可维护性。更多关于高效处理数据映射的实践和原理,你可以在技术文档板块找到深入的探讨。

附录:完整的通用 TypeHandler 实现(基于 MyBatis-Plus)

以下是整合了上述所有思路的完整实现代码,供你参考和使用:

package com.mmyf.commons.mybatis;

import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.postgresql.util.PGobject;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * PostgreSQL JSON 类型处理器(通用版本)
 * 通过 MyBatis-Plus 的 TableInfo 缓存获取实体类字段的实际类型,准确识别类型(包括泛型类型)
 * 
 * 使用方式:
 * 1. 在实体类中使用 @TableField(typeHandler = CommonJsonTypeHandler.class)
 * 2. 在 XML 中使用 typeHandler="com.mmyf.commons.mybatis.CommonJsonTypeHandler"
 * 
 * TypeHandler 会通过以下方式识别类型:
 * 1. 从 ResultSetMetaData 获取表名和列名
 * 2. 通过 MyBatis-Plus 的 TableInfoHelper 获取实体类信息(利用其缓存机制)
 * 3. 通过 TableFieldInfo 获取字段的实际类型(包括泛型类型)
 * 4. 缓存类型信息以提高性能
 * 
 * @author teddy
 * @date 2026-01-29
 */
@MappedJdbcTypes({JdbcType.OTHER})
public class CommonJsonTypeHandler<T> extends BaseTypeHandler<T> {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    // 类型缓存:表名 + 列名 -> TypeReference
    private static final Map<String, TypeReference<?>> TYPE_CACHE = new ConcurrentHashMap<>();

    private TypeReference<T> typeReference;

    // 构造函数传入的类型(用于兜底逻辑)
    private Class<T> fallbackType;

    /**
     * 默认构造函数 - MyBatis 会调用此构造函数
     */
    public CommonJsonTypeHandler() {
        this.typeReference = null;
    }

    /**
     * 构造函数 - 显式指定类型(用于测试或特殊场景)
     * 当无法从元数据获取类型信息时,会使用此类型作为兜底
     * 
     * <p><b>重要限制:</b>
     * <ul>
     *   <li>由于 Java 类型擦除,Class<T> 类型参数不支持泛型信息</li>
     *   <li>例如:List<String> 会被擦除为 List,泛型参数 String 会丢失</li>
     *   <li>如果需要处理泛型类型(如 List<String>、Map<String, Object>),请使用 
     *       {@link #CommonJsonTypeHandler(TypeReference)} 构造函数</li>
     * </ul>
     * 
     * <p><b>MyBatis 初始化说明:</b>
     * <ul>
     *   <li>MyBatis 在初始化 TypeHandler 时,通常只会传入 Class 类型(通过反射获取)</li>
     *   <li>由于类型擦除,无法获取泛型信息,因此此构造函数主要用于简单类型(非泛型)</li>
     *   <li>对于泛型类型,建议通过 XML 配置或注解配置,让 TypeHandler 从实体类字段的元数据中获取完整类型信息</li>
     * </ul>
     * 
     * @param type 目标类型(不支持泛型参数)
     */
    public CommonJsonTypeHandler(Class<T> type) {
        this.typeReference = null;
        this.fallbackType = type;
    }

    /**
     * 构造函数 - 显式指定 TypeReference(用于复杂泛型类型)
     */
    public CommonJsonTypeHandler(TypeReference<T> typeReference) {
        if (typeReference == null) {
            throw new IllegalArgumentException("TypeReference argument cannot be null");
        }
        this.typeReference = typeReference;
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        // 注意:CallableStatement 无法像 ResultSet 那样通过 getMetaData() 获取列名和表名元数据
        // 因此无法在此处初始化 typeReference
        // 
        // 如果 typeReference 未初始化(为 null),parseJson 方法会使用兜底逻辑:
        // 1. 优先使用构造函数传入的 fallbackType(如果通过 CommonJsonTypeHandler(Class<T> type) 构造)
        //    - 注意:fallbackType 不支持泛型参数(由于 Java 类型擦除)
        //    - 对于泛型类型(如 List<String>),会被擦除为原始类型(List),泛型参数丢失
        // 2. 如果 fallbackType 也为 null,返回原始 JSON 字符串(String 类型)
        //
        // 建议:
        // 1. 如果可能,优先使用 ResultSet 的 getNullableResult 方法,以便自动识别类型(支持泛型)
        // 2. 或者在调用存储过程前,通过其他方式(如 XML 映射配置)显式指定类型
        // 3. 如果需要处理泛型类型,使用 CommonJsonTypeHandler(TypeReference<T>) 构造函数
        // 4. 如果类型是简单类型(非泛型),可以使用 CommonJsonTypeHandler(Class<T>) 构造函数

        return parseJson(cs.getObject(columnIndex));
    }

    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 尝试从 ResultSetMetaData 获取表名和字段信息
        try {
            ResultSetMetaData metaData = rs.getMetaData();
            int columnCount = metaData.getColumnCount();

            // 找到对应的列索引
            int columnIndex = -1;
            for (int i = 1; i <= columnCount; i++) {
                if (columnName.equalsIgnoreCase(metaData.getColumnName(i)) || 
                    columnName.equalsIgnoreCase(metaData.getColumnLabel(i))) {
                    columnIndex = i;
                    break;
                }
            }

            if (columnIndex > 0) {
                initTypeFromResultSet(metaData, columnIndex, columnName);
            } else {
                // 如果找不到列索引,尝试通过列名推断
                initTypeFromColumnName(columnName);
            }
        } catch (Exception e) {
            // 如果获取元数据失败,尝试通过列名推断
            initTypeFromColumnName(columnName);
        }

        return parseJson(rs.getObject(columnName));
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        // 尝试从 ResultSetMetaData 获取表名和字段信息
        try {
            ResultSetMetaData metaData = rs.getMetaData();
            String columnName = metaData.getColumnName(columnIndex);
            initTypeFromResultSet(metaData, columnIndex, columnName);
        } catch (Exception e) {
            // 如果获取元数据失败,尝试通过列名推断
            try {
                String columnName = rs.getMetaData().getColumnName(columnIndex);
                initTypeFromColumnName(columnName);
            } catch (Exception ex) {
                // 忽略异常
            }
        }

        return parseJson(rs.getObject(columnIndex));
    }

    /**
     * 通过表名和列名获取类型信息(使用 MyBatis-Plus 的 TableInfo 缓存)
     * 根据数据库的 column 名从 MyBatis-Plus 元数据中查找,而不是通过属性名
     */
    @SuppressWarnings("unchecked")
    private void initTypeByTableAndColumn(String tableName, String columnName) {
        if (typeReference != null) {
            return; // 已经初始化
        }

        // 构建缓存 key(使用规范化后的表名和列名)
        String normalizedTableName = normalizeTableName(tableName);
        String cacheKey = normalizedTableName + "#" + columnName;

        // 先从缓存中获取
        TypeReference<?> cachedRef = TYPE_CACHE.get(cacheKey);
        if (cachedRef != null) {
            this.typeReference = (TypeReference<T>) cachedRef;
            return;
        }

        // 通过 MyBatis-Plus 的 TableInfoHelper 获取表信息(利用其缓存机制)
        // 先尝试使用完整表名,如果失败再尝试规范化后的表名
        TableInfo tableInfo = TableInfoHelper.getTableInfo(tableName);
        if (tableInfo == null && !normalizedTableName.equals(tableName)) {
            tableInfo = TableInfoHelper.getTableInfo(normalizedTableName);
        }

        // 如果还是找不到,遍历所有 TableInfo 查找匹配的表名
        if (tableInfo == null) {
            for (TableInfo info : TableInfoHelper.getTableInfos()) {
                String infoTableName = info.getTableName();
                // 处理带 schema 的表名匹配
                if (tableName.equals(infoTableName) || 
                    normalizedTableName.equals(normalizeTableName(infoTableName))) {
                    tableInfo = info;
                    break;
                }
            }
        }

        if (tableInfo == null) {
            return; // 找不到对应的表信息
        }

        // 通过数据库列名(column)查找 TableFieldInfo,而不是通过属性名
        TableFieldInfo fieldInfo = tableInfo.getFieldList().stream()
            .filter(f -> columnName.equalsIgnoreCase(f.getColumn()))
            .findFirst()
            .orElse(null);

        if (fieldInfo == null) {
            return; // 找不到字段
        }

        // 获取字段的实际类型(包括泛型)
        Field field = fieldInfo.getField();
        Type fieldType = field.getGenericType();
        TypeReference<?> typeRef = createTypeReference(fieldType);

        if (typeRef != null) {
            TYPE_CACHE.put(cacheKey, typeRef);
            this.typeReference = (TypeReference<T>) typeRef;
        }
    }

    /**
     * 规范化表名(去掉 schema 前缀,只保留表名)
     */
    private String normalizeTableName(String tableName) {
        if (tableName == null || tableName.isEmpty()) {
            return tableName;
        }
        // 处理带 schema 的表名,如 "db_mcp.t_api" -> "t_api"
        if (tableName.contains(".")) {
            return tableName.substring(tableName.lastIndexOf('.') + 1);
        }
        return tableName;
    }

    /**
     * 根据 Type 创建 TypeReference
     */
    private TypeReference<?> createTypeReference(Type type) {
        if (type == null) {
            return null;
        }

        // 使用 TypeReference 的匿名内部类来创建 TypeReference
        // 通过 ObjectMapper 的 TypeFactory 来构造 JavaType
        com.fasterxml.jackson.databind.JavaType javaType = objectMapper.getTypeFactory().constructType(type);

        return new TypeReference<Object>() {
            @Override
            public com.fasterxml.jackson.databind.JavaType getType() {
                return javaType;
            }
        };
    }

    /**
     * 根据参数类型初始化类型
     */
    private void initTypeByParameter(T parameter) {
        if (typeReference != null) {
            return; // 已经初始化
        }

        if (parameter == null) {
            return; // 无法从 null 推断类型
        }

        // 直接使用参数的实际类型创建 TypeReference
        Class<?> paramClass = parameter.getClass();
        this.typeReference = new TypeReference<T>() {
            @Override
            public com.fasterxml.jackson.databind.JavaType getType() {
                return objectMapper.getTypeFactory().constructType(paramClass);
            }
        };
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
        // 在设置参数时初始化类型信息
        if (typeReference == null) {
            initTypeByParameter(parameter);
        }

        try {
            String jsonString = objectMapper.writeValueAsString(parameter);
            PGobject jsonObject = new PGobject();
            jsonObject.setType("json");
            jsonObject.setValue(jsonString);
            ps.setObject(i, jsonObject);
        } catch (Exception e) {
            throw new SQLException("Error converting object to JSON string", e);
        }
    }

    /**
     * 从 ResultSetMetaData 初始化类型
     */
    private void initTypeFromResultSet(ResultSetMetaData metaData, int columnIndex, String columnName) throws SQLException {
        String tableName = getTableName(metaData, columnIndex);

        if (tableName != null && columnName != null) {
            initTypeByTableAndColumn(tableName, columnName);
        } else if (columnName != null) {
            // 如果无法获取表名,尝试在所有已注册的实体类中查找列
            initTypeByColumnNameOnly(columnName);
        }
    }

    /**
     * 仅通过列名初始化类型(在所有已注册的实体类中查找,使用 MyBatis-Plus 缓存)
     * 根据数据库的 column 名查找,而不是通过属性名
     */
    @SuppressWarnings("unchecked")
    private void initTypeByColumnNameOnly(String columnName) {
        if (typeReference != null) {
            return; // 已经初始化
        }

        // 遍历 MyBatis-Plus 缓存的所有 TableInfo
        for (TableInfo tableInfo : TableInfoHelper.getTableInfos()) {
            // 通过数据库列名(column)查找 TableFieldInfo,而不是通过属性名
            TableFieldInfo fieldInfo = tableInfo.getFieldList().stream()
                .filter(f -> columnName.equalsIgnoreCase(f.getColumn()))
                .findFirst()
                .orElse(null);

            if (fieldInfo != null) {
                // 找到字段,获取类型
                Field field = fieldInfo.getField();
                Type fieldType = field.getGenericType();
                TypeReference<?> typeRef = createTypeReference(fieldType);

                if (typeRef != null) {
                    String cacheKey = normalizeTableName(tableInfo.getTableName()) + "#" + columnName;
                    TYPE_CACHE.put(cacheKey, typeRef);
                    this.typeReference = (TypeReference<T>) typeRef;
                    return;
                }
            }
        }
    }

    /**
     * 通过列名初始化类型(备用方案)
     */
    private void initTypeFromColumnName(String columnName) {
        if (columnName != null) {
            initTypeByColumnNameOnly(columnName);
        }
    }

    /**
     * 从 ResultSetMetaData 获取表名
     */
    private String getTableName(ResultSetMetaData metaData, int columnIndex) throws SQLException {
        try {
            // 尝试获取表名(可能包含 schema,如 db_mcp.t_api)
            String tableName = metaData.getTableName(columnIndex);
            if (tableName != null && !tableName.isEmpty()) {
                // 处理带 schema 的表名,提取表名部分
                if (tableName.contains(".")) {
                    tableName = tableName.substring(tableName.lastIndexOf('.') + 1);
                }
                return tableName;
            }
        } catch (Exception e) {
            // 某些数据库驱动可能不支持 getTableName
        }
        return null;
    }

    /**
     * 解析 JSON 字符串为对象
     */
    @SuppressWarnings("unchecked")
    private T parseJson(Object value) throws SQLException {
        if (value == null) {
            return null;
        }

        String jsonString;
        if (value instanceof PGobject) {
            jsonString = ((PGobject) value).getValue();
        } else {
            jsonString = value.toString();
        }

        if (jsonString == null || jsonString.trim().isEmpty()) {
            return null;
        }

        try {
            if (typeReference != null) {
                // 优先使用:支持完整泛型信息
                return objectMapper.readValue(jsonString, typeReference);
            } else if (fallbackType != null) {
                // 兜底:使用构造函数传入的类型(不支持泛型参数)
                // 注意:对于泛型类型(如 List<UserInfo>),会被擦除为原始类型(List)
                // 反序列化后的元素类型可能不正确(如 List<UserInfo> 可能变成 List<LinkedHashMap>)
                return objectMapper.readValue(jsonString, fallbackType);
            } else {
                // 最后兜底:返回原始 JSON 字符串
                // 注意:这会导致类型不匹配,但不会抛出异常
                return (T) jsonString;
            }
        } catch (Exception e) {
            throw new SQLException("Error parsing JSON string to object: " + jsonString, e);
        }
    }
}




上一篇:工程师职场能见度提升指南:告别代码高手却无存在感的困境
下一篇:嵌入式GPIO的8种模式详解:从电路结构到STM32应用场景
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-7 19:24 , Processed in 0.308168 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表