Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >mybatis异常集之Cannot determine value type from string ‘xxx‘

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

原创
作者头像
lyb-geek
修改于 2020-07-03 09:48:43
修改于 2020-07-03 09:48:43
7.6K0
举报
文章被收录于专栏:Linyb极客之路Linyb极客之路

前言

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

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

示例

1、entity

代码语言:txt
AI代码解释
复制
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

代码语言:txt
AI代码解释
复制
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

代码语言:txt
AI代码解释
复制
<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、单元测试

代码语言:txt
AI代码解释
复制
 @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
单元测试结果1.png

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

代码语言:txt
AI代码解释
复制
Cannot determine value type from string 'xxx'

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

代码语言:txt
AI代码解释
复制
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
单元测试-无参构造器.png

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

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

代码语言:txt
AI代码解释
复制
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修改为如下

代码语言:txt
AI代码解释
复制
<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
单元测试-有参构造器.png

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

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

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

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

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

我们跟踪源码可以发现`

代码语言:txt
AI代码解释
复制
org.apache.ibatis.executor.resultset.DefaultResultSetHandler

这个类有个方法createResultObject

代码语言:txt
AI代码解释
复制
 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方法中

代码语言:txt
AI代码解释
复制
objectFactory.create(resultType)

其核心代码片段如下

代码语言:txt
AI代码解释
复制
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方法中

代码语言:txt
AI代码解释
复制
createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);

其核心代码片段如下

代码语言:txt
AI代码解释
复制
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 之间的转换。当转换异常,就会报

代码语言:txt
AI代码解释
复制
Cannot determine value type from string 'xxx'

总结

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

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

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

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

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

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
开发常用MyBatis的核心配置,你能看懂几个?
注意:这些子元素必须按照由上到下的顺序进行配置,否则MyBatis在解析XML配置文件的时候会报错。
千羽
2021/12/29
6360
开发常用MyBatis的核心配置,你能看懂几个?
mybatis解读篇
传统方式需要加载驱动,声明sql等操作,而现在的框架都像上面一样进行了各种封装,使得我们使用更加方便,下面来看看mybatis的方式。
用针戳左手中指指头
2021/01/29
8970
mybatis解读篇
MyBatis学习总结(一)——ORM概要与MyBatis快速入门
程序员应该将核心关注点放在业务上,而不应该将时间过多的浪费在CRUD中,多数的ORM框架都把增加、修改与删除做得非常不错了,然后数据库中查询无疑是使用频次最高、复杂度大、与性能密切相关的操作,我们希望得到一种使用方便,查询灵活的ORM框架,MyBatis可以满足这些要求,MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架,它也是SSM框架集成中的重要组成部分。
张果
2018/10/09
1.2K0
MyBatis学习总结(一)——ORM概要与MyBatis快速入门
硬菜要软着吃?不如试试这篇【MyBatis】
大家好,我是小菜,一个渴望在互联网行业做到蔡不菜的小菜。可柔可刚,点赞则柔,白嫖则刚!「死鬼~看完记得给我来个三连哦!」
蔡不菜丶
2020/11/11
4690
硬菜要软着吃?不如试试这篇【MyBatis】
Mybatis源码解析(七):查询数据库主流程
冬天vs不冷
2025/01/21
1640
Mybatis源码解析(七):查询数据库主流程
Mybatis基础
虽然我们能够通过JDBC来连接和操作数据库,但是哪怕只是完成一个SQL语句的执行,都需要编写大量的代码,更不用说如果我还需要进行实体类映射,将数据转换为我们可以直接操作的实体类型,JDBC很方便,但是还不够方便。
用户9645905
2023/10/29
3780
Mybatis基础
全网最全、最新MyBatis框架核心知识,收藏这一篇就够了!
?‍?作者:Java学术趴 ?仓库:Github、Gitee ✏️博客:CSDN、掘金、InfoQ、云+社区 ?公众号:Java学术趴 ?特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系小编
Java学术趴
2021/08/24
1.9K0
《Mybatis 手撸专栏》第11章:流程解耦,封装结果集处理器
上班就像打怪升级,拿着一把西瓜刀,从南天门砍到北天门。但时间长了,怪越来越凶了,西瓜刀也不得手了。咋办,在游戏里大家肯定是想办法换装备了、买武器了、学技能了,这样才能有机会打通更多的关卡。
小傅哥
2022/06/13
4590
《Mybatis 手撸专栏》第11章:流程解耦,封装结果集处理器
MyBatis从入门到精通(十)—源码剖析之延迟加载源码细节
在开发过程中很多时候我们并不需要总是在加载⽤户信息时就⼀定要加载他的订单信息。此时就是我 们所说的延迟加载。 举个栗⼦:
共饮一杯无
2022/11/28
3400
Mybatis 框架学习(四)——如果世界真的那么简单就好了
就是说我们在java代码中要写一个pojo类,与数据库表的字段相对应,必须名字相同。
RAIN7
2022/06/15
2860
Mybatis 框架学习(四)——如果世界真的那么简单就好了
mybatis学习之高级映射
根据文章内容撰写摘要总结。
用户1141560
2017/12/26
8600
mybatis学习之高级映射
MyBatis源码阅读(七) --- 查询结果集封装流程
前面一篇文章我们分析了mapper方法具体的执行流程,跟踪代码到了resultSetHandler.handleResultSets()处理结果集这里,这也是Mybatis处理数据的最后一个步骤了。
终有救赎
2024/01/30
4380
MyBatis源码阅读(七) --- 查询结果集封装流程
从源码角度分析 MyBatis 工作原理
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
2020labs小助手
2021/09/07
5090
MyBatis 多表操作
  关系数据库中第一个表中的单个行只可以与第二个表中的一个行相关,且第二个表中的一个行也只可以与第一个表中的一个行相关。
Demo_Null
2020/09/28
3340
MyBatis 多表操作
MyBatis源码分析一:核心组件
前面一篇文章MyBatis3使用 讲解了MyBatis的基本使用,这篇介绍MyBatis的核心组件,让我们从整体上了解MyBatis的组成。
心平气和
2021/01/13
4670
MyBatis源码分析一:核心组件
一文帮你搞定MyBatis的类型转换模块,深度好文,欢迎一键三连!!!
  MyBatis是一个持久层框架ORM框架,实现数据库中数据和Java对象中的属性的双向映射,那么不可避免的就会碰到类型转换的问题,在PreparedStatement为SQL语句绑定参数时,需要从Java类型转换为JDBC类型,而从结果集中获取数据时,则需要从JDBC类型转换为Java类型,所以我们来看下在MyBatis中是如何实现类型的转换的。
用户4919348
2021/09/08
1.5K0
一文帮你搞定MyBatis的类型转换模块,深度好文,欢迎一键三连!!!
MyBatis框架及原理分析
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
java架构师
2019/04/09
4180
MyBatis框架及原理分析
【小家MyBatis】MyBatis封装结果集时,Integer类型的id字段被赋值成了Long类型---读源码找原因
为了追查此问题根源,本人通过复现现象、控制变量、调试源码等方式,苦心全身心投入连续查找近4个小时,终于找出端倪。现通过本文分享给大家,希望对各位有所帮助。
YourBatman
2019/09/03
2.8K0
【小家MyBatis】MyBatis封装结果集时,Integer类型的id字段被赋值成了Long类型---读源码找原因
MyBatis笔记
三层架构包含的三层: 界面层:和用户打交道的,接收用户的请求参数,显示处理结果的。对应的包是controller包。 业务逻辑层:接收了界面层传递的数据,计算逻辑,调用数据库,获取数据。对应的包是service包。 数据访问层:就是访问数据库,执行对数据的查询,修改,删除等等。对应的包是dao包。 三层的处理请求的交互: 用户使用界面层–> 业务逻辑层—>数据访问层(持久层)–>数据库(mysql) 如图:
技术交流
2022/11/18
1.1K0
MyBatis笔记
MyBatis2
sqlSession = sessionFactory.openSession();
用户2038009
2021/03/07
5460
推荐阅读
相关推荐
开发常用MyBatis的核心配置,你能看懂几个?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档