版权所有,转载请注明出处,谢谢!
https://blog.csdn.net/gxkvji/article/details/97966198
问题
在开发中遇到了There is no getter for property…异常。然后看了下源码,分析了下原因,到底什么时候加 @Param,什么时候不加。
代码
mapper接口方法:
int selectStudentById(String id);
当xml文件如下时:
<select id="selectStudentById" parameterType="String" resultMap="StudentResult">
select * from table where
<if test = "id != null and id != '' ">
id = #{id}
</if>
</select>
会出现异常错误,应该把mapper接口改为:
int selectStudentById(@Param("id") String id);
总结:
- 当 xml 的 paramType 属性为 Map 时,接口中不需要加 @Param 注解。
- 当 xml 的 paramType 属性为 String ,Integer时,且xml中使用 < if > 标签,接口中需要加 @Param 注解。
- 当接口中只有一个属性,且xml中未使用 < if > 标签,不用加 @Param 标签。
- 当接口中有多个参数时,必须加入 @Param 标签。
源码分析:
下面篇幅较长,请耐心阅读。
mapper接口两种不同的方法:
public Expert selectExpertById(@Param("id") String id);
public Expert selectExpertById(String id);
xml方法实现:
<select id="selectExpertById" parameterType="java.lang.String" resultMap="ExpertResult">
select * from tbpm_expert where
<if test="id != null and id != '' ">
id = #{id}
</if>
</select>
- 首先会调用mybatis-3.4.0.jar中的 MapperProxy 代理类,调用MapperProxy类中的 invoke 方法。
//method:mapper中的接口方法;args:mapper中的参数值
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
- 之后会调用 MapperMethod 的 excute 方法, sqlCommandType 表示 xml 中的查询类型,是 select 还是 insert,此次查询返回单个实体类,继续调用类中的 convertArgsToSqlCommandParam 方法,把参数转化为 sql 语句需要的格式。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//SqlCommandType表示xml中的查询类型
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
//继续判断返回值类型
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);
}
//省略无关代码
return result;
}
- mapper参数有 @Param 从 method 中获取到参数信息,进行封装,返回Map对象,此时param为 {id:id值,param1:id值}。此时需要注意hasNamedParameters 参数,表示是否包含命名参数,即 mapper 接口中有没有加@Param。
没有 @Param 属性,并且 参数个数为1,返回mapper接口中的参数值,此时 param 是一个 String 对象。
public Object convertArgsToSqlCommandParam(Object[] args) {
//params是method中属性,此次查询参数是id
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasNamedParameters && paramCount == 1) {
//mapper中没有@Param属性,并且参数个数为1,返回参数值
return args[params.keySet().iterator().next().intValue()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
- 回到 excute 方法中,继续往下走到 sqlSession.selectOne(command.getName(),param),之后进行一系列跳转,进入 DynamicSqlSource 的 getBoundSql 方法,parameterObject 为 selectOne 中的param。
首先 new 一个 DynamicContext 对象,里面包含一个 bindings 属性,是一个Map,封装着参数 _parameter 和设置的数据库类型 _dataBases, _dataBases是 mybatis 数据源的类型(例如mysql)。
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
初始化 DynamicContext 时,parameterObject 不为空并且不是一个 Map 对象时,new 一个 MetaObject 对象,把 parameterObject 保存到里面,并且作为参数,初始化一个 ContextMap 对象。否则初始化 ContextMap 时,参数MetaObject 为 null,后面会用到 metaObject 是否为 null。
public DynamicContext(Configuration configuration, Object parameterObject) {
//parameterObject不为空并且不是map,new一个MetaObject对象,后面会用到
if (parameterObject != null && !(parameterObject instanceof Map)) {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
bindings = new ContextMap(metaObject);
} else {
bindings = new ContextMap(null);
}
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
- 回到 DynamicSqlSource 的 getboundSql 方法的 rootSqlNode.apply(context),把 xml 中的 sql 语句进行处理,此时如果 xml 中有 < if >标签,会进行处理获取到其中的常量,示例中的常量为 id 字段。
之后会再进入 DynamicContext,调用 getProperty 方法,参数:target 是上面的 bindings,name 为上面的常量 id。类中重写了 get方法。
public Object getProperty(Map context, Object target, Object name) throws OgnlException {
Map map = (Map) target;
//此处重写了get方法
Object result = map.get(name);
if (map.containsKey(name) || result != null) {
return result;
}
Object parameterObject = map.get(PARAMETER_OBJECT_KEY);
if (parameterObject instanceof Map) {
return ((Map) parameterObject).get(name);
}
return null;
}
- 当走到 map.get(name) 时,paramerMetaObject 即为上面的 metaObject,mapper接口中有 @Parma,parameterMetaObject 为 null,返回 null,没有 @Param 的时候,parameterMetaObject 不为 null,走 parameterMetaObject.getValue(strKey) 方法。最后会进入 Reflector 类的 getGetInvoker 方法,method 为 null 抛出 “There is no getter for peoperty named ‘id’ in…” 的异常信息,至此,我们就找到了报异常的地方。
新手小白,有错误的地方欢迎大家指正,谢谢。