使用Mybatis遇到的thereisnogetter异常
在使用mybatis的时候有时候会遇到一个问题就是明明参数是正确的,但是还是会提示There is no getter XXX
这个异常,但是一般的解决办法是在mapper里面添加@Param
注解来完成是别的,那么为什么会遇到这个问题呢?
创新互联-专业网站定制、快速模板网站建设、高性价比离石网站开发、企业建站全套包干低至880元,成熟完善的模板库,直接使用。一站式离石网站制作公司更省心,省钱,快速模板网站建设找我们,业务覆盖离石地区。费用合理售后完善,十余年实体公司更值得信赖。
以下为举例代码:
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"?>
如果执行上述的代码,你会发现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 Mapparam = new ParamMap