文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

mybatis报错Error attempting to get column ‘id‘ from result set. Cause: org.postgresql.util.PSQLExcept

2023-09-10 14:43

关注

mybatis报错Error attempting to get column ‘id’ from result set. Cause: org.postgresql.util.PSQLException: Bad value for type int : 493987884173376\n;

1、事件起因:公司变更原本的自增id转成snowid,但是在测试过程中出现了一个select语句报错了,代码无变更,报错的内容大致是接收的对象不应该使用id来接收,因为数据库变更后是个bigint,正常得拿long来接收,但是问题是sql查询的确实有id,可是接收的对象类中是没有id字段的,正常不会接收这个id参数才对。对象类和sql贴在下方。在这里插入图片描述


sql语句比较简单,仅仅是一个查询语句,附上sql和表结构

在这里插入图片描述

    <select id="selectAutoPullGroupConfig"            resultType="com.hujing.hujing_sc_bussiness.pojo.vo.channel2.AutoPullGroupConfigVo">        select * from qw_pull_group_config        where corpid = #{corpid}          and state = #{state}          and deleted = 0    </select>

对象的接收类如下,其中可以看到报错的id字段在数据库中是存在的,但是对象类中并没有id字段:

@Datapublic class AutoPullGroupConfigVo {    private Integer lastGroupSurplus;    private Integer pullGroupDays;    @NotNull    private Integer qrcodeType;    private String userConfig;    private Integer usingQrcodeOrder;    private Long createrId;    private String createrName;    private Long createrDeleted;}

后来根据报错显示:

Error attempting to get column ‘id’ from result set.

也就是说在resultset中的id字段是存在问题的

我们找到了mybatis对接数据库,进行查询接收数据的流程如下,从建立连接到取回结果集,最终会拿到一个resultset

在这里插入图片描述

当我们拿到了resultset,接下来我们要做的肯定是和接收结果的对象类来做映射,问题呢就出现在这个地方,我们正好回顾一下取结果的过程。

→数据库在执行query方法的过程中,最后会去拿handleResultSets的结果,执行的过程我标红处理

  @Override  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {    PreparedStatement ps = (PreparedStatement) statement;    ps.execute();    return resultSetHandler.handleResultSets(ps);  }
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {  try {    if (parentMapping != null) {      // 这里处理多结果集的嵌套映射,不分析      handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);    } else {      if (resultHandler == null) {        // 如果用户没有指定对结果的处理器ResultHandler,那么默认会使用DefaultResultHandler        DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);        // 对结果集进行转换        handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);        // 将解析后的结果添加到multiResults中        // 如果没有指定ResultHandler,那么默认会将解析之后的结果添加到multipleResults中        multipleResults.add(defaultResultHandler.getResultList());      } else {        // 用户定义了对结果集的处理方法,即ResultHandler        // 那么使用ResultSetHandler处理之后,会将结果再交给ResultHandler进行处理        handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);      }    }  } finally {    // issue #228 (close resultsets)    closeResultSet(rsw.getResultSet());  }}
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {  // 判断是否有嵌套的映射  if (resultMap.hasNestedResultMaps()) {    ensureNoRowBounds();    checkResultHandler();    // 处理嵌套映射(rsw是取回的结果集,resultmap是接收的对象类)    handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);  } else {    // 处理简单映射    handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);  }}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)      throws SQLException {  // ResultContext用来存放结果对象  DefaultResultContext<Object> resultContext = new DefaultResultContext<>();  // 获取jdbc查询结果集  ResultSet resultSet = rsw.getResultSet();  // 取出rowbounds中的offset,跳过结果集中的前面offset行  skipRows(resultSet, rowBounds);  // 判断是否需要接着处理,shouldProcessMoreRows判断当前处理的行数是否已经超过了rowbounds中limit指定的行数  while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {    ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);    Object rowValue = getRowValue(rsw, discriminatedResultMap, null);    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);  }}
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {  final ResultLoaderMap lazyLoader = new ResultLoaderMap();  // 通过构造方法来创建结果对象,可能会用到构造方法参数映射  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);  // 判断结果值是否为空,并且没有对应当前结果java类型的typehandler  if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {    final MetaObject metaObject = configuration.newMetaObject(rowValue);    // 判断是否使用了我们定义的映射    boolean foundValues = this.useConstructorMappings;    // 判断是否需要使用自动映射,来设置没有映射对应的字段    if (shouldApplyAutomaticMappings(resultMap, false)) {      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;    }    // 对其他resultMapping处理    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;    foundValues = lazyLoader.size() > 0 || foundValues;    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;  }  return rowValue;}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {  // 用来判断是否使用到了构造方法参数映射  this.useConstructorMappings = false; // reset previous mapping result  final List<Class<?>> constructorArgTypes = new ArrayList<>();  final List<Object> constructorArgs = new ArrayList<>();  Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);  // 当前结果不为空,并且不存在可以直接将ResultSet转换为指定java类型的typeHandler  if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();    for (ResultMapping propertyMapping : propertyMappings) {      // issue gcode #109 && issue #149      // 懒加载,单独一篇文章来分析      if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {        resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);        break;      }    }  }  // 如果结果对象不为空,并且构造方法使用到了构造参数映射,那么将useConstructorMappings设置为true  this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result  return resultObject;}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)      throws SQLException {  final Class<?> resultType = resultMap.getType();  final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);  // 取出构造函数参数的映射,就是FLAG为CONSTRUCTOR的映射  final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();  if (hasTypeHandlerForResultObject(rsw, resultType)) {    // 如果符合当前java结果类型的TypeHandler,那么会使用typehandler来对结果集进行处理    return createPrimitiveResultObject(rsw, resultMap, columnPrefix);  } else if (!constructorMappings.isEmpty()) {    // 如果指定了构造参数映射,使用构造参数映射来进行构造    return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);  } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {    // 如果是结果或者有默认构造函数,那么直接通过ObjectFactory来创建    return objectFactory.create(resultType);  } else if (shouldApplyAutomaticMappings(resultMap, false)) {    // 判断是否开启了自动映射    return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);  }  throw new ExecutorException("Do not know how to create an instance of " + resultType);}

6、问题主要出现在这里!!

我们可以看到最后返回接收对象的结果类,是使用了构造函数来进行构造的,可是按照我们的对象类正常是无参构造才对,因为我们只用了@Data注解,正常情况是没有构造函数的,但是这里却走进了默认的构造函数。问题出现如下:

    private Object createByConstructorSignature(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {        //获取所有声明的构造函数        Constructor<?>[] constructors = resultType.getDeclaredConstructors();        //查找默认的构造函数        Constructor<?> defaultConstructor = this.findDefaultConstructor(constructors);         //如果找到了默认的构造函数,则调用该构造函数进行对象的创建        if (defaultConstructor != null) {            return this.createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, defaultConstructor);        } else {        //如果没有找到默认的构造函数,则需要根据结果集中jdbcTypes集合的内容判断是否可以使用构造函数创建对象            Constructor[] var7 = constructors;            int var8 = constructors.length;            for(int var9 = 0; var9 < var8; ++var9) {                Constructor<?> constructor = var7[var9];                if (this.allowedConstructorUsingTypeHandlers(constructor, rsw.getJdbcTypes())) {                    return this.createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, constructor);                }            }            throw new ExecutorException("No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames());        }    }

在这里插入图片描述

我们发现拿回来的构造函数是带有一个integer类型的,而mybatis对于构造函数的结果处理如下,其中会拿出构造函数的属性,那么拿到的会是一个integer,而根据取回的rsw所拿到的字段,第一个拿到的colunname是id,所以我们到现在明白了第一个问题 —> mybatis会根据构造函数进行一一对应,这个id对应的integer类型是在这里拿到的,那么问题回到了这个构造函数为什么会带一个integer呢???

在这里插入图片描述

我们发现这个integer是通过上面resultType.getDeclaredConstructors();拿到的,那么我们做了一个测试,就是脱离mybatis,单从jdk的角度确认他反射出来的对象的构造方法是什么样子的。

    @Test    public void text() {        Class a = AutoPullGroupConfigVo.class;        Constructor<?>[] constructors = a.getDeclaredConstructors();    }

我们拿到的这个构造方法如下,发现这个构造方法也是带有Integer的,也就是说明他和mybatis无关,我们注意到这个对象类有一个特别之处在于使用了@NotNull这个注解,我们测试了一下去掉NotNull注解

在这里插入图片描述

下面是去掉NotNull注解的情况,发现变成了无参构造,那么问题就出现在了NotNull注解上

在这里插入图片描述

由此我们知道了,在mybatis结果集映射中,因为受到构造函数的影响,造成了值的对应错误,同时notNull注解会更改构造函数的结构,处理方法我们可以增加@NoArgsConstructor 和@AllArgsConstructor变为有参和无参构造,但是请注意,这样会使notNull的单参数构造失效,以上就是本次错误的排查总结过程。

附:我又测试了单全参注解,和单无参注解,得到的结果是:只有全参构造也报错,因为需要字段与类参数一一对应,无参构造则不走这个对应规则,只有无参不报错,这个以后继续来看mybatis的映射规则吧。

来源地址:https://blog.csdn.net/weixin_43225116/article/details/126510180

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯