使用Mybatis遇到的there is no getter异常

在使用mybatis的时候有时候会遇到一个问题就是明明参数是正确的,但是还是会提示There is no getter XXX这个异常,但是一般的解决办法是在mapper里面添加@Param注解来完成是别的,那么为什么会遇到这个问题呢?

以下为举例代码:

Mapper层代码

public interface Pro1_Mapper {

  Pro1_Studnet insertStu(Pro1_Studnet pro1_studnet);

}

实体类代码

public class Pro1_Studnet {

  private String stuId;

  private String stuName;

  private String stuClass;

  private String stuTeacher;

  public String getStuId() {
    return stuId;
  }

  public void setStuId(String stuId) {
    this.stuId = stuId;
  }

  public String getStuName() {
    return stuName;
  }

  public void setStuName(String stuName) {
    this.stuName = stuName;
  }

  public String getStuClass() {
    return stuClass;
  }

  public void setStuClass(String stuClass) {
    this.stuClass = stuClass;
  }

  public String getStuTeacher() {
    return stuTeacher;
  }

  public void setStuTeacher(String stuTeacher) {
    this.stuTeacher = stuTeacher;
  }
}

Main方法

public static void main(String[] args) {
    Logger logger = null;
    logger = Logger.getLogger(Pro1_Main.class.getName());
    logger.setLevel(Level.DEBUG);
    SqlSession sqlSession = null;
    try {
      sqlSession = study.mybatis.MybatisUtil.getSqlSessionFActory().openSession();
      Pro1_Mapper pro1_Mapper = sqlSession.getMapper(Pro1_Mapper.class);
      Pro1_Studnet pro1_studnet =new Pro1_Studnet();
      pro1_studnet.setStuName("张三");
      Pro1_Studnet pro1_studnet1 =pro1_Mapper.insertStu(pro1_studnet);
      System.out.println(pro1_studnet1.getStuClass());
      sqlSession.commit();
    } finally {
      sqlSession.close();
    }
  }

XML文件

<?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="study.szh.demo.project1.Pro1_Mapper">
  <resultMap type="study.szh.demo.project1.Pro1_Studnet" id="pro1_stu">
    <result property="stuId" column="stu_id"/>
    <result property="stuName" column="stu_name"/>
    <result property="stuClass" column="stu_class"/>
    <result property="stuTeacher" column="stu_teacher"/>
  </resultMap>
  <select id="insertStu" parameterType="study.szh.demo.project1.Pro1_Studnet" resultMap="pro1_stu">
    SELECT * from pro_1stu where stu_name = #{pro1_studnet.stuName};
  </select>
</mapper>

如果执行上述的代码,你会发现mybatis会抛出一个异常:
There is no getter for property named 'pro1_studnet' in 'class study.szh.demo.project1.Pro1_Studnet'
很明显就是说pro1_studnet这个别名没有被mybatis正确的识别,那么将这个pro1_studnet去掉呢?

尝试将xml文件中的pro1_studnet去掉然后只保留stuName,执行代码:

张三

这表明程序运行的十分正常,但是在实际的写法中,还有如果参数为String也会导致抛出getter异常,所以此次正好来分析下

分析

mybatis是如何解析mapper参数的

跟踪源码你会发现在MapperProxyinvoke处会进行入参:

@Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
   if (Object.class.equals(method.getDeclaringClass())) {
    return method.invoke(this, args);
   } else if (isDefaultMethod(method)) {
    return invokeDefaultMethod(proxy, method, args);
   }
  } catch (Throwable t) {
   throw ExceptionUtil.unwrapThrowable(t);
  }
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
 }

注意此处的args,这个参数就是mapper的入参。

那么mybatis在这里接收到这个参数之后,它会将参数再一次进行传递,此时会进入到MapperMethodexecute方法

public Object execute(SqlSession sqlSession, Object[] args) {
  //省略无关代码
   case SELECT:
    if (method.returnsVoid() && method.hasResultHandler()) {
     executeWithResultHandler(sqlSession, args);
     result = null;
    } else if (method.returnsMany()) {
     result = executeForMany(sqlSession, args);
    } else if (method.returnsMap()) {
     result = executeForMap(sqlSession, args);
    } else if (method.returnsCursor()) {
     result = executeForCursor(sqlSession, args);
    } else {
     Object param = method.convertArgsToSqlCommandParam(args);
     result = sqlSession.selectOne(command.getName(), param);
    }
    break;
   case FLUSH:
    result = sqlSession.flushStatements();
    break;
   default:
    throw new BindingException("Unknown execution method for: " + command.getName());
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
   throw new BindingException("Mapper method '" + command.getName()
     + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result;
 }

因为在xml文件里面使用的是select标签,所以会进入case的select,然后此时会进入到Object param = method.convertArgsToSqlCommandParam(args); 在这里args还是Stu的实体类,并未发生变化

随后进入convertArgsToSqlCommandParam方法,然后经过一个方法的跳转,最后会进入到ParamNameResolvergetNamedParams方法,

public Object getNamedParams(Object[] args) {
  final int paramCount = names.size();
  if (args == null || paramCount == 0) {
   return null;
  } else if (!hasParamAnnotation && paramCount == 1) {
   return args[names.firstKey()];
  } else {
   final Map<String, Object> param = new ParamMap<Object>();
   int i = 0;
   for (Map.Entry<Integer, String> entry : names.entrySet()) {
    param.put(entry.getValue(), args[entry.getKey()]);
    // add generic param names (param1, param2, ...)
    final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
    // ensure not to overwrite parameter named with @Param
    if (!names.containsValue(genericParamName)) {
     param.put(genericParamName, args[entry.getKey()]);
    }
    i++;
   }
   return param;
  }
 }

此时注意hasParamAnnotation这个判断,这个判断表示该参数是否含有标签,有的话在这里会在Map里面添加一个参数,其键就是GENERIC_NAME_PREFIX(param) + i 的值。像在本次的测试代码的话,会直接在return args[names.firstKey()];返回,不过这不是重点,继续往下走,会返回到MapperMethodexecute方法的这一行result = sqlSession.selectOne(command.getName(), param);

此时的param就是一个Stu对象了。

继续走下去...由于mybatis的调用链太多,此处只会写出需要注意的点,可以在自己debug的时候稍微注意下。

BaseExecutorcreateCacheKey的方法

@Override
 public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  if (closed) {
   throw new ExecutorException("Executor was closed.");
  }
  CacheKey cacheKey = new CacheKey();
  cacheKey.update(ms.getId());
  cacheKey.update(rowBounds.getOffset());
  cacheKey.update(rowBounds.getLimit());
  cacheKey.update(boundSql.getSql());
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
  // mimic DefaultParameterHandler logic
  for (ParameterMapping parameterMapping : parameterMappings) {
   if (parameterMapping.getMode() != ParameterMode.OUT) {
    Object value;
    String propertyName = parameterMapping.getProperty();
    if (boundSql.hasAdditionalParameter(propertyName)) {
     value = boundSql.getAdditionalParameter(propertyName);
    } else if (parameterObject == null) {
     value = null;
    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
     value = parameterObject;
    } else {
     MetaObject metaObject = configuration.newMetaObject(parameterObject);
     value = metaObject.getValue(propertyName);
    }
    cacheKey.update(value);
   }
  }
  if (configuration.getEnvironment() != null) {
   // issue #176
   cacheKey.update(configuration.getEnvironment().getId());
  }
  return cacheKey;
 }

当进行到这一步的时候,由于mybatis的类太多了,所以这里选择性的跳过,当然重要的代码还是会介绍的。

DefaultReflectorFactory的findForClass方法

@Override
 public Reflector findForClass(Class<?> type) {
  if (classCacheEnabled) {
      // synchronized (type) removed see issue #461
   Reflector cached = reflectorMap.get(type);
   if (cached == null) {
    cached = new Reflector(type);
    reflectorMap.put(type, cached);
   }
   return cached;
  } else {
   return new Reflector(type);
  }
 }

注意MetaObjectgetValue方法:

 public Object getValue(String name) {
  PropertyTokenizer prop = new PropertyTokenizer(name);
  if (prop.hasNext()) {
   MetaObject metaValue = metaObjectForProperty(prop.getIndexedName());
   if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
    return null;
   } else {
    return metaValue.getValue(prop.getChildren());
   }
  } else {
   return objectWrapper.get(prop);
  }
 }

这里的name的值是pro1_stu.stuName,而prop的属性是这样的:

这里的hasNext函数会判断这个prop的children是不是为空,如果不是空的话就会进入 get 方法,然后进入到如下的方法通过返回获取get方法。

所以当遍历到stuName的时候会直接return,

然后就需要注意ReflectorgetGetInvoker方法,

public Invoker getGetInvoker(String propertyName) {
  Invoker method = getMethods.get(propertyName);
  if (method == null) {
   throw new ReflectionException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
  }
  return method;
 }

这个propertyName就是pro1_studnet,而getMethods.get(propertyName);就是要通过反射获取pro1_studnet方法,但是很明显,这里是获取不到的,所以此时就会抛出这个异常。

那么为什么加了@param注解之后就不会抛出异常呢

此时就需要注意MapWrapper类的get方法。

 @Override
 public Object get(PropertyTokenizer prop) {
  if (prop.getIndex() != null) {
   Object collection = resolveCollection(prop, map);
   return getCollectionValue(prop, collection);
  } else {
   return map.get(prop.getName());
  }
 }

在之前就说过,如果加了注解的话,map的结构是{"param1","pro1_studnet","pro1_studnet",XXX对象},此时由于prop的index是null,所以会直接返回map的键值为pro1_studnet的对象。

而在DefaultReflectorFactoryfindForClass里面,由于所加载的实体类已经包含了Pro1_Student,随后在metaValue.getValue(prop.getChildren());的将stu_name传入过去,就可以了获取到了属性的值了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Mybatis foreach标签使用不当导致异常的原因浅析

    异常产生场景及异常信息 上周,由于在Mybatis的Mapper接口方法中使用实现了Map.Entry接口的泛型类,同时此方法对应的sql语句也使用了foreach标签,导致出现了异常.如下为异常信息: org.apache.ibatis.exceptions.PersistenceException: ### Error updating database. Cause: org.apache.ibatis.reflection.ReflectionException: There is no

  • 使用Mybatis遇到的there is no getter异常

    在使用mybatis的时候有时候会遇到一个问题就是明明参数是正确的,但是还是会提示There is no getter XXX这个异常,但是一般的解决办法是在mapper里面添加@Param注解来完成是别的,那么为什么会遇到这个问题呢? 以下为举例代码: Mapper层代码 public interface Pro1_Mapper { Pro1_Studnet insertStu(Pro1_Studnet pro1_studnet); } 实体类代码 public class Pro1_Stud

  • MyBatis版本升级导致OffsetDateTime入参解析异常问题复盘

    背景 最近有一个数据统计服务需要升级 SpringBoot 的版本,由 1.5.x.RELEASE 直接升级到 2.3.0.RELEASE ,考虑到没有用到 SpringBoot 的内建 SPI ,升级过程算是顺利.但是出于代码洁癖和版本洁癖,看到项目中依赖的 MyBatis 的版本是 3.4.5 ,相比当时的最新版本 3.5.5 大有落后,于是顺便把它升级到 3.5.5 .升级完毕之后,执行所有现存的集成测试,发现有部分 OffsetDateTime 类型入参的查询方法出现异常,于是进行源码层

  • Mybatis单个参数的if判断报异常There is no getter for property named 'xxx' in 'class java.lang.Integer'的解决方案

    我们都知道mybatis在进行参数判断的时候,直接可以用<if test=""></if> 就可以了,如下: 1.常规代码 <update id="update" parameterType="com.cq2022.zago.order.entity.Test" > update t_test_l <set > <if test="trnsctWayId != null"

  • Mybatis单个参数的if判断报异常There is no getter for property named 'xxx' in 'class java.lang.Integer'的解决方案

    我们都知道mybatis在进行参数判断的时候,直接可以用<if test=""></if> 就可以了,如下: 1.常规代码 <update id="update" parameterType="com.cq2022.zago.order.entity.Test" > update t_test_l <set > <if test="trnsctWayId != null"

  • Mybatis Generator最完美配置文件详解(完整版)

    最近没做项目,重新整理了一个最完整的Mybatis Generator(简称MBG)的最完整配置文件,带详解,再也不用去看EN的User Guide了: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"

  • 详解在springboot中使用Mybatis Generator的两种方式

    介绍 Mybatis Generator(MBG)是Mybatis的一个代码生成工具.MBG解决了对数据库操作有最大影响的一些CRUD操作,很大程度上提升开发效率.如果需要联合查询仍然需要手写sql.相信很多人都听说过微服务,各个微服务之间是松耦合的.每个微服务仅关注于完成一件任务并很好地完成该任务.在一个微服务的开发过程中很可能只关注对单表的操作.所以MBG在开发过程中可以快速的生成代码提升开发效率. 本文将说到在springboot的项目中如何去配置(XML形式和Java配置类形式)和使用M

  • Mybatis通过数据库表自动生成实体类和xml映射文件

    环境:maven+idea. 1. 需要的jar包 基本的spring和mybatis依赖包就不说了,在pom文件的build->plugins节点下需要添加(两个依赖包也可以直接添加到pom的依赖里面去,这里是为了直接通过maven的插件来生成.如果不是使用maven,自行百度下): <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-mave

  • mybatis分页插件pageHelper详解及简单实例

    mybatis分页插件pageHelper详解及简单实例 工作的框架spring springmvc mybatis3 首先使用分页插件必须先引入maven依赖,在pom.xml中添加如下 <!-- 分页助手 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5

  • mybatis 事务回滚配置操作

    在mybatis进行批量操作的时候,插入多条数据时,设置回滚但是前面几条还是插入,经过尝试 问题所在: 官网api上openSession(false)就可以回滚了,但是用session.getConnection().getAutoCommit()查看还是true 解决方法: 将DataSource配置改为AutoCommit(false) 将conn设置setAutoCommit(false),用conn进行提交,回滚操作 例子: SqlSession session = sqlSessio

  • mybatis 如何通过resultMap 返回long

    mybatis resultMap 返回long <resultMap id="ResultOfLong" type="java.lang.Long"> <result column="budget" property="budget" javaType="long"/> </resultMap> mybatis long 类型返回为null报异常 Mapper meth

随机推荐