在使用mybatis的时候有时候会遇到一个问题就是明明参数是正确的,但是还是会提示there is no getter xxx
这个异常,但是一般的解决办法是在mapper里面添加@param
注解来完成是别的,那么为什么会遇到这个问题呢?
以下为举例代码:
mapper层代码
1
2
3
4
5
|
public interface pro1_mapper { pro1_studnet insertstu(pro1_studnet pro1_studnet); } |
实体类代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
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方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?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
处会进行入参:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@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
方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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
方法,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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
的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
@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方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@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
方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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
方法,
1
2
3
4
5
6
7
|
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
方法。
1
2
3
4
5
6
7
8
9
|
@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
传入过去,就可以了获取到了属性的值了。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://segmentfault.com/a/1190000016376666