在使用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