专栏首页Linyb极客之路mybatis异常集之Cannot determine value type from string ‘xxx‘
原创

mybatis异常集之Cannot determine value type from string ‘xxx‘

前言

本文的创作来源于朋友在自学mybatis遇到的问题,问题如文章标题所示Cannot determine value type from string 'xxx'。他在网上搜索出来的答案基本上都是加上一个无参构造器,就可以解决问题。他的疑问点在于他实体没有使用无参构造器,而使用了有参构造器,有的查询方法不会报错,有的查询方法却报错了。下面将演示他出现的这种场景的示例。

注: mybatis的搭建过程忽略,仅演示案例。案例代码取自朋友

示例

1、entity

public class Student {

    private int id;
    private String name;
    private String email;
    private int age;


    public Student(String aa,int bb){
        System.out.println("===============执行student的有参数构造方法 aa = "+aa+" bb = "+bb+"================");
    }


    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }



    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", age=" + age +
                '}';
    }
}

2、dao

public interface StudentDao {

    Student getStudentById(int id);

    List<Student> getStudents(@Param("myname") String name, @Param("myage") int age);

    List<Student> getStudentByObj(Student student);


}

3、mapper.xml

<mapper namespace="com.academy.dao.StudentDao">


    <select id="getStudentById" resultType="com.academy.domain.Student">
        select id, name, email, age from student where id = #{sid}
    </select>

    <select id="getStudents" resultType="com.academy.domain.Student">
        select id, name, email, age from student where name = #{myname} or age = #{myage}
    </select>


    <select id="getStudentByObj" resultType="com.academy.domain.Student">
        select id, name, email, age from student where name = #{name} or age = #{age}
    </select>


</mapper>

4、单元测试

 @Test
    public void testgetStudentById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentDao dao = sqlSession.getMapper(StudentDao.class);
        Student student = dao.getStudentById(1034);
        sqlSession.close();
        System.out.println(student);
    }

    @Test
    public void testgetStudents(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentDao dao = sqlSession.getMapper(StudentDao.class);
        List<Student> students = dao.getStudents("张三", 22);
        sqlSession.close();
        students.forEach(student -> System.out.println(student));
    }

5、运行单元测试

单元测试结果1.png

从截图看出,当实体没有使用无参构造器时,出现朋友所说的有一些方法成功,一些方法报错,报错信息为

Cannot determine value type from string 'xxx'

采用网上介绍的方法,给实体加上无参构造器,如下:

public class Student {

    private int id;
    private String name;
    private String email;
    private int age;
    
    public Student(){
        
    }


    public Student(String aa,int bb){
        System.out.println("===============执行student的有参数构造方法 aa = "+aa+" bb = "+bb+"================");
    }

再次运行单元测试

单元测试-无参构造器.png

加上无参构造器,确实不报错。那我们是否就可以因为这样,就得出mybatis执行必须得加上无参构造器的结论呢?

我们再把实体的无参构造器去掉,如下

public class Student {

    private int id;
    private String name;
    private String email;
    private int age;
    


    public Student(String aa,int bb){
        System.out.println("===============执行student的有参数构造方法 aa = "+aa+" bb = "+bb+"================");
    }

同时把mapper.xml修改为如下

<mapper namespace="com.academy.dao.StudentDao">


    <select id="getStudentById" resultType="com.academy.domain.Student">
        select id, name, email, age from student where id = #{sid}
    </select>

    <select id="getStudents" resultType="com.academy.domain.Student">
        select  name, age from student where name = #{myname} or age = #{myage}
    </select>


    <select id="getStudentByObj" resultType="com.academy.domain.Student">
        select id, name, email, age from student where name = #{name} or age = #{age}
    </select>

然后再次运行单元测试

单元测试-有参构造器.png

从截图可以看出,mybatis加了有参构造器并不影响执行。只是有参构造器要成功运行的条件是

  • mapper.xml中查询的数据库字段属性的类型要和有参构造器的字段类型一一匹配
  • 其次查询字段的个数要和有参构造器个数一样

比如该示例的有参构造器为string int,则xml中select语句的字段类型也得是varchar和int

解密Cannot determine value type from string 'xxx'异常

一开始我们看到这个异常,我们可能会先去检查实体字段和数据库字段是不是一样,首先这个思路是没问题,一旦发现不是这个问题,我们可以转换一下思路,先预设一下可能出现这种问题场景,比如有没有可能是mybatis在执行数据库字段到实体字段类型映射的过程中出现转换错误。其次解决异常的终极大招就是带着问题去跟踪源码。

我们跟踪源码可以发现`

org.apache.ibatis.executor.resultset.DefaultResultSetHandler

这个类有个方法createResultObject

 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);
    final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
    if (hasTypeHandlerForResultObject(rsw, resultType)) {
      return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
    } else if (!constructorMappings.isEmpty()) {
      return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
    } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
      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);
  }

这个方法是根据结果集返回值的类型创建出相应的bean字段对象

1、当实体使用无参构造器时

mybatis会调用createResultObject方法中

objectFactory.create(resultType)

其核心代码片段如下

private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    try {
      Constructor<T> constructor;
      if (constructorArgTypes == null || constructorArgs == null) {
        constructor = type.getDeclaredConstructor();
        try {
          return constructor.newInstance();
        } catch (IllegalAccessException e) {
          if (Reflector.canControlMemberAccessible()) {
            constructor.setAccessible(true);
            return constructor.newInstance();
          } else {
            throw e;
          }
        }
      }
      constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[0]));
      try {
        return constructor.newInstance(constructorArgs.toArray(new Object[0]));
      } catch (IllegalAccessException e) {
        if (Reflector.canControlMemberAccessible()) {
          constructor.setAccessible(true);
          return constructor.newInstance(constructorArgs.toArray(new Object[0]));
        } else {
          throw e;
        }
      }
    } catch (Exception e) {
      String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList)
          .stream().map(Class::getSimpleName).collect(Collectors.joining(","));
      String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList)
          .stream().map(String::valueOf).collect(Collectors.joining(","));
      throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);
    }
  }

使用无参构造器创建对象

2、当实体使用有参构造参数

mybatis会调用createResultObject方法中

createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);

其核心代码片段如下

private Object createUsingConstructor(ResultSetWrapper rsw, Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) throws SQLException {
    boolean foundValues = false;
    for (int i = 0; i < constructor.getParameterTypes().length; i++) {
      Class<?> parameterType = constructor.getParameterTypes()[i];
      String columnName = rsw.getColumnNames().get(i);
      TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
      Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
      constructorArgTypes.add(parameterType);
      constructorArgs.add(value);
      foundValues = value != null || foundValues;
    }
    return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
  }

这个代码片段里面有个TypeHandler,这个是mybatis的类型处理器,用来处理 JavaType 与 JdbcType 之间的转换。

由代码我们看出,当实体使用有参构造函数时,会遍历有参构造参数个数,根据有参构造参数下标查找相应的数据库字段名称,根据有参构造字段类型以及数据库字段名称找类型处理器。然后使用TypeHandler来处理JavaType 与 JdbcType 之间的转换。当转换异常,就会报

Cannot determine value type from string 'xxx'

总结

解决Cannot determine value type from string 'xxx'的方法有2种

  • 实体加无参构造参数
  • mapper.xml中查询的数据库字段属性的类型要和有参构造器的字段类型一一匹配;查询字段的个数要和有参构造器个数一样

最后当出现异常时,带着问题去跟踪源码,有时候会比利用搜索引擎更容易得到答案

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 企业API接口设计(token、timestamp、sign)之具体实现

    Token:访问令牌access token, 用于接口中, 用于标识接口调用者的身份、凭证,减少用户名和密码的传输次数。一般情况下客户端(接口调用方)需要先向...

    lyb-geek
  • jvm垃圾回收以及垃圾回收器

    ? ? ? ? ? ? ? 垃圾回收器 ? ? ? ? ? ?

    lyb-geek
  • 操作日志追踪记录之MDC入门

    MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。某些...

    lyb-geek
  • 深入Java源码剖析之Set集合

    HashSet实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用 ...

    wangweijun
  • 【程序源码】女神节特供

    今天3月8号喽,女神节,咱们还是程序猿,最容易孤生的一个人种,得看别人各种虐,小编这就不乐意了,所以奉上祖传源码助大家一臂之力告别单身,各位,瞧好了。 效果...

    编程范 源代码公司
  • 表白必备:C语言实现会变色的心

    各位,今天我们用C语言搞点好玩的,给大家带来一个会自动变色的心,话不多说,上源码:

    7089bAt@PowerLi
  • JSON.parse()和JSON.stringify()

    parse用于从一个字符串中解析出json对象,如 var str = '{"name":"huangxiaojian","age":"23"}' 结果: JS...

    用户1220053
  • 这样的开源应用你相信它吗?

    任何读过马修•加勒特作品的人都知道很多专门用来销售的软件是不靠谱的。一些Linux倡导者宣称开放自由而且免费的软件比封闭的专门用来谋利的软件更安全,然而这里有一...

    哲洛不闹
  • 这样的开源应用你相信它吗?

    任何读过马修•加勒特作品的人都知道很多专门用来销售的软件是不靠谱的。一些Linux倡导者宣称开放自由而且免费的软件比封闭的专门用来谋利的软件更安全,然而这里有一...

    哲洛不闹
  • __str__

    当打印一个类的实例时,返回的字符串是对象的地址信息,如<__main__.Student object at 0x109afb310>,很不好看 可通过在类...

    py3study

扫码关注云+社区

领取腾讯云代金券