MyBatis源码的学习(21)---mybatis中当参数只有一个且是自定义对象类型时提示reflection.ReflectionException: There is no getter

陌路散爱 提交于 2020-01-09 01:03:04

 Error querying database.  Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'user' in 'class learn.User'
### Cause: org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'user' in 'class learn.User'
主要原因是,复制代码的时候,入参那里将@Param注解去掉后,结果仍然使用 #{user.userCode}的方式获取值。

下面是正确的示例代码:


package learn;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.session.RowBounds;

import java.util.List;

public interface UserMapper {

  public User selectUser(String id);

  public User selectUserByIdAndName(String id,String name);

  public User selectUserByArgs(String id,String name);

  public User selectUserByIdFromTable(@Param("id")String id,@Param("tableName")String tableName);

  public User selectUser2(User user);

  public void updateName(@Param("name") String name , @Param("id")String id);

  void insert(User user);

  void batchInsert(List<User> list);

  public List<User> selectUserFromTable(@Param("tableName")String tableName, RowBounds rowBounds);
  //使用注解式的sql注入
  @Select("select * from u_user where usercode = #{简单类型只有一个参数的时候随便写}")
  public  User selectOne(String id);

  //使用注解式的sql注入
//使用了注解时,我们的入参是map,最终的元数据类型是map,所以需要 user.userCode
  @Select("select * from u_user where usercode = #{user.userCode}")
  public  User selectOneByUser(@Param("user") User user);
  
 //使用注解式的sql注入
  @Select("select * from u_user where usercode = #{param1.userCode}")
  public  User selectOneByUser3(@Param("user") User user);

  //使用对象型,参数可以随便写,这时候不需要 参数.属性,而是直接用属性名就可以了
 //因为我们最终的mateObject对象就是普通类型,所以可以直接 userCode 获取值
  @Select("select * from u_user where usercode = #{userCode}")
  public  User selectOneByUser2(User user666);

}

关键代码:

package org.apache.ibatis.reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

public class ParamNameResolver {

  public static final String GENERIC_NAME_PREFIX = "param";

  private final SortedMap<Integer, String> names;

  private boolean hasParamAnnotation;

  public ParamNameResolver(Configuration config, Method method) {
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
//使用了注解的时候,走这个逻辑
//最终的names中的map
//{"0"-->"user"}
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
//未使用注解时,如果是高版本
//最终的names中的map
//{"0","arg0"}
      if (name == null) {
        // @Param was not specified.
        if (config.isUseActualParamName()) {
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }


  /**
   * 使用单个入参时
   * 关键在于,获取参数的时候
   * 如果使用了注解时,会封装一个map数据,map中俩个值
   *  会多出来一个key = param1 的一条数据
   *  另一个是 key=user 的数据
   *  俩条数据,key不同,但是value值是一样的
   * 如果未使用注解时,返回的就是 args[0],也就是我们的user对象
   * 其实对比一下,也就发现问题所在了,使用注解的时候,我们将入参又封装了一次,封装到了map结构中
   */
  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<>();
      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;
    }
  }
}

在我们的sql调用的时候:

单个对象的入参,入参对象就是这个对象

使用了注解的单个对象,入参是一个map,里面有俩条数据。

下面是我们的默认参数处理器DefaultParameterHandler中的setParameters()的操作:为啥我们使用注解的时候,必须要用#{user.userCode},而单个入参时,可以直接用#{属性名}的方式。另外单个入参时,我们的形参是可以随便写的,如果是简单类型,直接返回我们的值(所以我们的#{这里随便写});如果是对象类型,使用元数据方式直接获取属性字段的值(所以用#{属性名称});使用注解时,我们的元数据中封装的是map,所以使用#{user.userCode}或者#{param1.userCode}才能够获取到值。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!