使用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参数的
跟踪源码你会发现在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
传入过去,就可以了获取到了属性的值了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读