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

MyBatis自定义TypeHandler的getResult方法未触发问题排查

问题:MyBatis自定义TypeHandler的getNullableResult方法未触发,列表字段无法正确映射

我知道复杂嵌套的实体结构会提升开发复杂度,但当前业务场景下必须解决这个问题。我的实体存在对象嵌套和对象列表的结构,具体如下:

实体定义

基础实体BaseEntity

@Data
public class BaseEntity {
    private Long id;
    private String name;
    private String description;
}

嵌套实体ImpactEntity

@Data
public class ImpactEntity implements Structable{
    private String ref;
    private String ASOF;
    private Long deliverTime;
    private String SLAMissed;
    private BaseEntity client;
    private BaseEntity application;
    private BaseEntity deliverable;
}

最终实体IncidentEntity

@Data
public class IncidentEntity {
    private String PID;
    private String ID;
    private String NID;
    private String REF;
    private String RREF;
    private String ASOF;
    private Long StartTime;
    private Long endTime;
    private String description;
    private String active;
    private Long createAt;
    private List<ImpactEntity> impacts;
}

MyBatis Mapper配置

<?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="org.well.valley.mapper.IncidentMapper" >
    <resultMap id="IncidentEntity" type="org.well.valley.entity.IncidentEntity">
        <result column="PID" jdbcType="VARCHAR" property="PID" />
        <result column="ID" jdbcType="VARCHAR" property="ID" />
        <result column="NID" jdbcType="VARCHAR" property="NID" />
        <result column="REF" jdbcType="VARCHAR" property="REF" />
        <result column="RREF" jdbcType="VARCHAR" property="RREF" />
        <result column="ASOF" jdbcType="VARCHAR" property="ASOF" />
        <result column="STARTTIME" jdbcType="INTEGER" property="startTime" />
        <result column="ENDTIME" jdbcType="INTEGER" property="endTime" />
        <result column="DESCRIPTION" jdbcType="VARCHAR" property="description" />
        <result column="ACTIVE" jdbcType="VARCHAR" property="active" />
        <result column="CREATEAt" jdbcType="INTEGER" property="createAt" />
        <result property="impacts" column="IMPACTS" typeHandler="org.well.valley.utils.typehandlers.OracleTypeHandler" />
    </resultMap>
    <select id="get" statementType="CALLABLE" parameterType="org.well.valley.entity.IncidentEntity">
        call get_incidents(
            #{ASOF, jdbcType=VARCHAR},
            #{ref, jdbcType=VARCHAR},
            #{active, jdbcType=VARCHAR},
            #{outParam.c1, mode=OUT, jdbcType=CURSOR, javaType=java.sql.ResultSet, resultMap=IncidentEntity}
        )
    </select>
</mapper>

自定义OracleTypeHandler实现

我写了一个通用型的OracleTypeHandler来处理impacts字段,计划支持包括List在内的结构(目前List的处理逻辑还未实现):

public class OracleTypeHandler<T> extends BaseTypeHandler<T> {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private ObjectMapper objectMapper = new ObjectMapper();
    private DBMapperBean getDBMapperBean() {
        return SpringContext.getBean(DBMapperBean.class);
    }
    private Class<T> type;
    protected int getIndex(ResultSetMetaData data, String columnName) throws SQLException {
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>11111111111111111111111111");
        for (int i = 1; i <= data.getColumnCount(); i++) {
            String c = data.getColumnName(i);
            if(columnName.equals(c)) {
                return i;
            }
        }
        return -1;
    }
    public OracleTypeHandler(Class<T> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
        logger.info(">>>>>>>>>>>>>>>>>>>>>>Type is:{}", type);
    }
    @Override
    public void setNonNullParameter(PreparedStatement ps, int index, T parameter, JdbcType jdbcType) throws SQLException {
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>333333333333333333333333");
        Connection conn = ps.getConnection();
        if(parameter.getClass().isArray()) {
            if(parameter instanceof String[]) {
                Array array = ((OracleConnection) conn).createOracleArray("STRINGARRAY", parameter);
                ps.setArray(index, array);
                array.free();
            }
        }
    }
    @Override
    public T getNullableResult(ResultSet resultSet, String columnName) throws SQLException {
        ResultSetMetaData metaData = resultSet.getMetaData();
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>444444444444444444444444444444");
        int index = getIndex(metaData, columnName);
        String columnClassName = metaData.getColumnClassName(index);
        if(columnClassName.equals("oracle.jdbc.OracleStruct")) {
            java.sql.Struct o = (java.sql.Struct)resultSet.getObject(index);
            return extractObject(o);
        }else if(columnClassName.equals("oracle.jdbc.OracleArray")) {
            Array array = resultSet.getArray(columnName);
            Object r = array.getArray();
            array.free();
            return (T) r;
        }
        return null;
    }
    private String typeNameConvert(String type, String target) {
        if(target.equals("DBTYPE")) {
            return type.concat("TYPE");
        }else {
            return type.substring(0, type.length()-4);
        }
    }
    private T extractObject(Struct o) throws SQLException {
        String typeName = o.getSQLTypeName();
        typeName = typeName.substring(typeName.lastIndexOf(".")+1);DBMapperBean dbMapperBean = getDBMapperBean();
        typeName = typeNameConvert(typeName, "JAVATYPE");
        if(typeName.endsWith("ARRAY")) {
            switch (typeName){
                case "STRINGARRAY":
                    break;
            }
        }else {
            HashMap <String, Class> map = dbMapperBean.getMap();
            try {
                Class<?> cls = (Class<?>) map.get(typeName);
                Structable obj = (Structable) cls.getDeclaredConstructor().newInstance();
                Object[] attrs = o.getAttributes();
                List<Field> fields = obj.getOrderedField(obj.getClass().getDeclaredFields());
                int i = 0;
                for (Field f : fields) {
                    f.setAccessible(true);
                    Object source = attrs[i];
                    if(source == null) {
                        f.set(obj, null);
                    } else {
                        Class targetType = f.getType();
                        Class sourceType = source.getClass();
                        if (Number.class.isAssignableFrom(targetType) && Number.class.isAssignableFrom(sourceType)) {
                            if (targetType == Long.class) {
                                source = Long.parseLong(source.toString());
                            }
                        }
                        try {
                            f.set(obj, source);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                    i++;
                }
                return (T) obj;
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>66666666666666666");
        Struct struct =(java.sql.Struct) rs.getObject(columnIndex);
        return extractObject(struct);
    }
    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>7777777777777777777");
        Struct struct =(java.sql.Struct) cs.getObject(columnIndex);
        return extractObject(struct);
    }
}

现象与疑问

调用存储过程前,OracleTypeHandler的构造函数已被正常调用,日志输出如下:

19:58:28.439 INFO o.w.v.u.t.OracleTypeHandler - >>>>>>>>>>>>>>>>>>>>>>Type is:class org.well.valley.entity.BaseEntity
19:58:33.692 INFO o.w.v.u.t.OracleTypeHandler - >>>>>>>>>>>>>>>>>>>>>>Type is:class org.well.valley.entity.BaseEntity
20:02:43.477 INFO o.w.v.u.t.OracleTypeHandler - >>>>>>>>>>>>>>>>>>>>>>Type is:class org.well.valley.entity.BaseEntity
20:02:47.161 INFO o.w.v.u.t.OracleTypeHandler - >>>>>>>>>>>>>>>>>>>>>>Type is:class org.well.valley.entity.BaseEntity
20:03:00.370 INFO o.w.v.u.t.OracleTypeHandler - >>>>>>>>>>>>>>>>>>>>>>Type is:class [Ljava.lang.String;
20:03:16.080 INFO o.w.v.u.t.OracleTypeHandler - >>>>>>>>>>>>>>>>>>>>>>Type is:interface java.util.List

但调用存储过程后,TypeHandler中的三个getNullableResult方法均未被触发——我原本期望通过列名获取结果的getNullableResult(ResultSet resultSet, String columnName)会被执行。目前impacts返回空列表,但直接调用数据库存储过程能得到正确结果。

想请教:

  1. getNullableResult方法未触发的原因是什么?
  2. MyBatis此时使用哪个TypeHandler处理impacts列表?

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 09:03:43