中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

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

發布時間:2020-10-21 00:38:50 來源:腳本之家 閱讀:299 作者:Somersames 欄目:編程語言

在使用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遇到的there is no getter異常

那么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遇到的there is no getter異常

繼續走下去...由于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的屬性是這樣的:

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

這里的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傳入過去,就可以了獲取到了屬性的值了。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

土默特右旗| 鲁甸县| 吉安市| 日喀则市| 泾川县| 隆子县| 勐海县| 息烽县| 垦利县| 珲春市| 大丰市| 改则县| 奉化市| 沙坪坝区| 商城县| 罗平县| 灯塔市| 汕尾市| 错那县| 准格尔旗| 永定县| 灌阳县| 德江县| 武山县| 丹寨县| 綦江县| 济阳县| 新巴尔虎右旗| 尼木县| 和平县| 建阳市| 侯马市| 策勒县| 安化县| 乌审旗| 陵水| 沁阳市| 玉门市| 二连浩特市| 吴旗县| 滨州市|