您好,登錄后才能下訂單哦!
在使用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參數的
跟蹤源碼你會發現在MapperProxy
的invoke
處會進行入參:
@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在這里接收到這個參數之后,它會將參數再一次進行傳遞,此時會進入到MapperMethod
的execute
方法
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
方法,然后經過一個方法的跳轉,最后會進入到ParamNameResolver
的getNamedParams
方法,
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()];
返回,不過這不是重點,繼續往下走,會返回到MapperMethod
的execute
方法的這一行result = sqlSession.selectOne(command.getName(), param);
此時的param就是一個Stu對象了。
繼續走下去...由于mybatis的調用鏈太多,此處只會寫出需要注意的點,可以在自己debug的時候稍微注意下。
BaseExecutor
的createCacheKey
的方法
@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); } }
注意MetaObject
的getValue
方法:
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,
然后就需要注意Reflector
的getGetInvoker
方法,
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
的對象。
而在DefaultReflectorFactory
的findForClass
里面,由于所加載的實體類已經包含了Pro1_Student,隨后在metaValue.getValue(prop.getChildren());
的將stu_name
傳入過去,就可以了獲取到了屬性的值了。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。