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

Posted limiry

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis报错Error attempting to get column ‘id‘ from result set. Cause: org.postgresql.util.PSQLExcept相关的知识,希望对你有一定的参考价值。

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贴在下方。


2、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>

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


@Data
public 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;


4、后来根据报错显示:

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

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

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

5、当我们拿到了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的映射规则吧。

Error attempting to get column 'e_gender' from result set

今天在使用mybatis查询时遇到报错

Error attempting to get column ‘e_gender‘ from result set.  Cause: java.sql.SQLException: Cannot convert value ‘男‘ from column 3 to TIMESTAMP.
; Cannot convert value ‘男‘ from column 3 to TIMESTAMP.; nested exception is java.sql.SQLException: Cannot convert value ‘男‘ from column 3 to TIMESTAMP.

在网上查了很多解决办法,大多都是说是数据库字段类型与实体类的属性类型的定义不一致的原因,但是请看:

<resultMap type="com.xyw.hospital.model.entity.Employee" id="BaseMap">
        <result property="eId" column="e_id" jdbcType="INTEGER"/>
        <result property="eName" column="e_name" jdbcType="VARCHAR"/>
        <result property="eGender" column="e_gender" jdbcType="VARCHAR"/>
        <result property="eBirthday" column="e_birthday" jdbcType="TIMESTAMP"/>
        <result property="eDid" column="e_did" jdbcType="INTEGER"/>
        <result property="eRoleid" column="e_roleid" jdbcType="INTEGER"/>
        <result property="eTel" column="e_tel" jdbcType="VARCHAR"/>
        <result property="eVprice" column="e_vprice" jdbcType="NUMERIC"/>
        <result property="eState" column="e_state" jdbcType="INTEGER"/>
        <result property="eCreateTime" column="e_create_time" jdbcType="TIMESTAMP"/>
        <result property="eClossTime" column="e_closs_time" jdbcType="TIMESTAMP"/>
    </resultMap>

    <sql id="items">
        e_id, e_name, e_gender, e_birthday, e_did, e_roleid, e_tel, e_vprice, e_state, e_create_time, e_closs_time    </sql>
    <!--查询单个-->
    <select id="queryById" resultMap="BaseMap">
        select
          <include refid="items"/>
        from employee
            where e_id = #{eId}
    </select>
CREATE TABLE `employee` (
  `e_id` int(11) NOT NULL AUTO_INCREMENT,
  `e_name` varchar(25) DEFAULT NULL,
  `e_gender` varchar(4) DEFAULT NULL COMMENT ‘‘‘男‘‘或‘‘女‘‘‘,
  `e_birthday` date DEFAULT NULL,
  `e_did` int(11) DEFAULT NULL,
  `e_roleid` int(11) DEFAULT NULL,
  `e_tel` varchar(15) DEFAULT NULL,
  `e_vprice` decimal(10,2) DEFAULT NULL,
  `e_state` int(1) DEFAULT ‘0‘ COMMENT ‘0 新注册(默认) 1 在职 2离职‘,
  `e_create_time` date DEFAULT NULL,
  `e_closs_time` date DEFAULT NULL,
  PRIMARY KEY (`e_id`) USING BTREE,
  KEY `idx_did` (`e_did`) USING BTREE,
  KEY `idx_roleid` (`e_roleid`) USING BTREE,
  KEY `idx_state` (`e_state`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
	protected Integer eId;
    protected String eName;
    protected String eGender;
    protected Date eBirthday;
    protected Integer eDid;
    protected Integer eRoleid;
    protected String eTel;
    protected Double eVprice;
    protected Integer eState;
    protected Date eCreateTime;
    protected Date eClossTime;

    public Employee(String eName, String eGender, Date eBirthday, String eTel, Integer eDid, Integer eRoleid, Double eVprice) {
        //略
    }

可以看到字段定义一致,导致一致我一直不得其解。

这个地方真正的原因是什么呢?

大家也许已经看到了,我这里有个构造方法,其中省去了一个字段,导致查到的数据多余构造方法中的数据,使得其将e_id->eName,e_name->eGender,e_gender->eBirthday,这里因为e_gender是vchar,而eBirthday是date类型,导致的类型转换失败。

这里又回到类型问题了,关于类型转换的问题这里不详说。但是大家记得在定义数据库字段与实体类字段的类型一定要一致

而这里的问题就在于mybatis在进行赋值时会使用实体类的构造方法进行赋值,如果构造方法中的参数个数与mysql结果集中的字段数量不一致时,特别是少了前面的参数,赋值时会按照顺序进行赋值,就有可能会出错。

这里的解决办法很简单,只要在实体类中添加一个空的构造方法

public Employee() {
    }

即可,mybatis会优先使用最适合的构造方法进行赋值,如果找不到适合的构造方法,就会使用空的构造方法,在赋值时会从实体类的属性中查找与结果集中字段名相同的属性进行赋值。

这让我想起以前在刚学习javaweb时老师总是强调空的构造方法的重要性,却始终不能理解,这次的出错让我记得以后对于pojo类的定义时,如果需要定义有参的构造方法,一定要把无参构造方法给补上,防止出现类似问题。

以上都是本人自己的理解,可能会与真正的解答不一致,欢迎各位大佬指正。

以上是关于mybatis报错Error attempting to get column ‘id‘ from result set. Cause: org.postgresql.util.PSQLExcept的主要内容,如果未能解决你的问题,请参考以下文章

Mybatis Error attempting to get column 'xxx' from result set. Cause: java.sql.SQLDataExcepti

Error attempting to get column 'e_gender' from result set

React 报错 Attempted import error: 'injectGlobal' is not exported from 'styled-components&

为什么连接HBase报错:Will not attempt to authenticate using SASL (unknown error)?

mongodb使用mongo报错: Error: couldn't connect to server 127.0.0.1:27017, connection attempt failed:

Error attempting to get column from result set