mybatis参数There is no getter for property…异常问题

版权所有,转载请注明出处,谢谢!
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>
  1. 首先会调用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);
 }
  1. 之后会调用 MapperMethodexcute 方法, 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;
}
  1. mapper参数有 @Parammethod 中获取到参数信息,进行封装,返回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;
    }
}
  1. 回到 excute 方法中,继续往下走到 sqlSession.selectOne(command.getName(),param),之后进行一系列跳转,进入 DynamicSqlSourcegetBoundSql 方法,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());
}
  1. 回到 DynamicSqlSourcegetboundSql 方法的 rootSqlNode.apply(context),把 xml 中的 sql 语句进行处理,此时如果 xml 中有 < if >标签,会进行处理获取到其中的常量,示例中的常量为 id 字段。

之后会再进入 DynamicContext,调用 getProperty 方法,参数:target 是上面的 bindingsname 为上面的常量 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;
}
  1. 当走到 map.get(name) 时,paramerMetaObject 即为上面的 metaObject,mapper接口中有 @ParmaparameterMetaObject 为 null,返回 null,没有 @Param 的时候,parameterMetaObject 不为 null,走 parameterMetaObject.getValue(strKey) 方法。最后会进入 Reflector 类的 getGetInvoker 方法,method 为 null 抛出 “There is no getter for peoperty named ‘id’ in…” 的异常信息,至此,我们就找到了报异常的地方。

新手小白,有错误的地方欢迎大家指正,谢谢。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值