好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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

查看更多关于使用Mybatis遇到的there is no getter异常的详细内容...

  阅读:50次