在使用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()]); final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1 ); 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(); 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 ) { 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) { 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
传入过去,就可以了获取到了属性的值了。