Mybatis04__输入参数/输出参数

一、输入参数源码分析

  1、参数解析器解析参数(得到一个Map集合 names,默认是{arg0,arg1,arg2....},如果某一个位置使用了@Param注解,那么该位置的argx会被注解的值替代)

public ParamNameResolver(Configuration config, Method method) {
	// 批量获取参数类型,我这里参数类型分别是Integer、String、String
    final Class<?>[] paramTypes = method.getParameterTypes();
	// 批量获取标注了@Param注解的参数,用一个二维数组封装,由于我只在id上标注了一个@Param("userId")
	// 二维数组的长度与传入的参数相同,有几个参数二维数组的长度便是多少,标注了@Param注解的值用一维数组封装
	// 所以最终的二维数组如下:[Annotation[1],Annotation[0],Annotation[0],]
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
	// 获取二维数组的长度,由于传入了三个参数,所以长度为3
    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) {
          hasParamAnnotation = true;
		  // 获取一维数组中的值,name的值为 userId
          name = ((Param) annotation).value();
          break;
        }
      }
	  // 如果不是使用@Param注解标注的参数,并且传入了实际参数,那么根据paramIndex的值去获取对应的值{arg0,arg1,arg2}
	  // 例如我们这里第二个参数使用的是 userName,那么它对应的就是 name就是arg1,第三个参数password对应的就是 arg2
      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就是{0:userId,1:arg1,2:arg2}
      map.put(paramIndex, name);
    }
	// 通过Collections工具类将map转换成一个SortedMap names={0:userId,1:arg1,2:arg2}
    names = Collections.unmodifiableSortedMap(map);
  }

   2、通过动态代理获取xxxMapper接口的代理类,然后通过invoke(...)执行目标方法,Object[] args 数组中封装的是实际传入的参数[实际参数1,实际参数2,实际参数3....]

// 动态代理执行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 (method.isDefault()) {
		if (privateLookupInMethod == null) {
		  return invokeDefaultMethodJava8(proxy, method, args);
		} else {
		  return invokeDefaultMethodJava9(proxy, method, args);
		}
	  }
	} catch (Throwable t) {
	  throw ExceptionUtil.unwrapThrowable(t);
	}
	final MapperMethod mapperMethod = cachedMapperMethod(method);
	// 关键方法,此处的args就是实际传入的参数 Object[2,"李四","24"]
	return mapperMethod.execute(sqlSession, args);
}

   3、根据操作的类型来选择不同的操作方法

// 通过command.getType()方法来判断当前类型是INSERT/UPDATE/DELETE/SELECT
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
		// 返回值是否为空
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
		// 返回值是否为数组
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
		// 是否使用@MapKey注解
        } 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);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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;
  }

  4、将实际参数转换成Sql命令参数

public Object convertArgsToSqlCommandParam(Object[] args) {
  return paramNameResolver.getNamedParams(args);
}

  5、获取最终的参数集合

public Object getNamedParams(Object[] args) {
	// 这里面的names就是我们通过参数解析器获得的SortedMap names={0:userId,1:arg1,2:arg2}
	// 此处的args就是实际传入的参数 args = Object[2,"李四","24"]
    final int paramCount = names.size();
	// 如果没有传入实际参数,或者参数的名称个数为0,直接返回null
    if (args == null || paramCount == 0) {
      return null;
	  // 如果没有使用@Parma注解并且只有一个参数,那么直接返回第一个参数
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
	  // names={0:userId,1:arg1,2:arg2}  args = Object[2,"李四","24"]
	  // 遍历names,并且将相应的数据存入Map集合param中,存入的规则如下
	  // param = {userId:2,arg1:"李四",arg2:"24"}
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        param.put(entry.getValue(), args[entry.getKey()]);

		// GENERIC_NAME_PREFIX的值为常量 param
        final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
        // 如果names中不包含param1、param2...等等数据,那么集合param中继续存入一份数据
		// {param1:2,param2:"李四",param3:"24"}
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);
        }
        i++;
      }
	  // 最终的Map集合 param={userId:2,arg1:"李四",arg2:"24",param1:2,param2:"李四",param3:"24"}
	  // 相当于是对传入的参数使用了两套保存机制
      return param;
    }
  }
}

 

二、实际案例结合上述源码  

  1、UserMapper接口

public interface UserMapper {
    // 根据id、userName、password三个条件查询用户信息
    public abstract User queryUserByThreeConditions(@Param("userId") Integer id, String userName, String password);
}

  2、UserMapper.xml

  根据下面的源码分析,可以得出如下结论,当传入多个参数的时候,如果没有使用@Param注解对参数进行标注,那么param={arg0:2,arg1:"李四",arg2:"24",param1:2,param2:"李四",param3:"24"},如果某个参数使用了@Param注解的话,那么对应的位置的 argx 就会被@Param("value")中的value值所替代,

  所以我们这个例子中param={userId:2,arg1:"李四",arg2:"24",param1:2,param2:"李四",param3:"24"},只要能确保传入的最终参数是{2,"李四","24"},你可以使用各种各样的方式去param这个集合中取取值,例如下面的几种方式都是可以的

<!--根据id、userName、password三个条件查询用户信息-->
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
	WHERE ID=#{param1} AND USER_NAME=#{param2} AND PASSWORD=#{param3}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
	WHERE ID=#{userId} AND USER_NAME=#{arg1} AND PASSWORD=#{arg2}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
	WHERE ID=#{userId} AND USER_NAME=#{param2} AND PASSWORD=#{param3}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
	WHERE ID=#{param1} AND USER_NAME=#{arg1} AND PASSWORD=#{arg2}
</select>

  3、测试类

public class TestMybatis {
    @Test
    public void test01() throws IOException {
        SqlSession session = TestMybatis.openSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = mapper.queryUserByThreeConditions(2, "李四", "24");
        System.out.println(user);
    }
}

 

三、输入参数

  1、单个参数

    01、传入基本类型数据  

<!--根据主键ID查询用户信息-->
// UserMapper接口
public abstract User queryUserById(Integer id);
// UserMapper.xml
<select id="queryUserById" resultType="com.mybatis.entity.User">
	// 取值的时候可以使用#{任意字符}获得传入的参数
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user WHERE ID=#{xxx}
</select>

    02、传入POJO类型

<!--根据id和userName查询用户信息-->
// UserMapper接口
public abstract User queryUserByIdAnduserName(User user);
// UserMapper.xml
<select id="queryUserByIdAnduserName" resultType="com.mybatis.entity.User">
	// 对于传入的是POJO类型的参数,取值的时候需要使用#{POJO的属性名}来取值
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user 
	WHERE ID=#{id} AND USER_NAME=#{userName}
</select>

  

  2、多个参数(这种情况下可以配合@Param("value")注解,可以做到见名知意)

<!--根据id、userName、password三个条件查询用户信息-->
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
	WHERE ID=#{param1} AND USER_NAME=#{param2} AND PASSWORD=#{param3}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
	WHERE ID=#{userId} AND USER_NAME=#{arg1} AND PASSWORD=#{arg2}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
	WHERE ID=#{userId} AND USER_NAME=#{param2} AND PASSWORD=#{param3}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
	WHERE ID=#{param1} AND USER_NAME=#{arg1} AND PASSWORD=#{arg2}
</select>

 

  3、传入Map集合

传入Map集合(mybatis本来就是要把输入的参数转化为map存起来,既然你已经传入了一个map集合,直接通过键取值就可以了)
map:{"id":1,"password":23}

public abstract User queryUserByIdAndPassword(Map<String,Object> map);

// 这里的id、password对应的是传入进来的Map集合的key
<select id="queryUserByIdAndPassword" resultType="com.mybatis.entity.User">
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
	WHERE ID=#{id} AND PASSWORD=#{password}
</select>

 

  4、传入Integer类型数组  

//根据多个用户id查询出多条用户记录
public abstract List<User> queryUsersByIds(Integer[] ids);

<!--根据多个用户id查询出多条用户记录-->
<select id="queryUsersByIds" resultType="com.mybatis.domain.User">
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM tbl_user
		<where>
			ID in
			<foreach collection="array" item="id"  open="(" close=")" separator=",">
				 #{id}
			</foreach>
		</where>
</select>

 

  5、传入List集合 

//根据多个用户id查询出多条用户记录
public abstract List<User> queryUsersByIds(List<Integer> ids);

<!--根据多个用户id查询出多条用户记录-->
<select id="queryUsersByIds" resultType="com.mybatis.domain.User">
	SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM tbl_user
		<where>
			ID in
			<foreach collection="list" item="id"  open="(" close=")" separator=",">
				 #{id}
			</foreach>
		</where>
</select>

  

四、输出参数

  1、resultType,返回值类型为实体类对象:自动封装,将数据库中的数据自动封装到javaBean中

 // 根据用户EmployeeId查询用户信息
    public abstract Employee queryEmployeeByEmployeeId(Employee employee);
<select id="queryEmployeeByEmployeeId" resultType="com.mybatis.entity.Employee">
        SELECT
            e.EMPLOYEE_ID,
            e.EMPLOYEE_NAME,
            e.EMPLOYEE_PASSWORD,
            e.EMPLOYEE_AGE,
            e.DEPARTMENT_ID
        FROM
          employee e
        WHERE
          e.EMPLOYEE_ID=#{employeeId}
    </select> 
// 测试结果
Employee{employeeId=3, employeeName='王五', employeePassword='789', employeeAge='25', departmentId=3}

  

  2、resultType,返回值类型为map

// 根据用户EmployeeId查询用户信息
public abstract Map<String,Object> queryEmployeeByEmployeeId(Employee employee);
<select id="queryEmployeeByEmployeeId" resultType="java.util.Map">
	SELECT
		e.EMPLOYEE_ID,
		e.EMPLOYEE_NAME,
		e.EMPLOYEE_PASSWORD,
		e.EMPLOYEE_AGE,
		e.DEPARTMENT_ID
	FROM
	  employee e
	WHERE
	  e.EMPLOYEE_ID=#{employeeId}
</select>
// 测试结果
{EMPLOYEE_ID=3, EMPLOYEE_AGE=25, DEPARTMENT_ID=3, EMPLOYEE_NAME=王五, EMPLOYEE_PASSWORD=789}

 

  3、如果查询的结果是多条记录,而又指定返回值为map集合,这个时候就不能使用mybatis的自动封装,这个时候要为多条记录指定主键,使用@MapKey("数据库中的列名")  

// EMPLOYEE_ID对应的是数据库的字段
@MapKey("EMPLOYEE_ID")
public abstract Map<String,Object> queryEmployeeByEmployeeId(Employee employee);
// 根据employeeAge模糊查询用户信息
<select id="queryEmployeeByEmployeeId" resultType="java.util.Map">
	SELECT
		e.EMPLOYEE_ID,
		e.EMPLOYEE_NAME,
		e.EMPLOYEE_PASSWORD,
		e.EMPLOYEE_AGE,
		e.DEPARTMENT_ID
	FROM
	  employee e
	WHERE
	  e.EMPLOYEE_AGE
	LIKE
	  "%"#{employeeAge}"%"
</select>
// 测试结果
{
	1={EMPLOYEE_ID=1, EMPLOYEE_AGE=23, DEPARTMENT_ID=1, EMPLOYEE_NAME=张三, EMPLOYEE_PASSWORD=123}, 
	2={EMPLOYEE_ID=2, EMPLOYEE_AGE=24, DEPARTMENT_ID=2, EMPLOYEE_NAME=李四, EMPLOYEE_PASSWORD=456}, 
	3={EMPLOYEE_ID=3, EMPLOYEE_AGE=25, DEPARTMENT_ID=3, EMPLOYEE_NAME=王五, EMPLOYEE_PASSWORD=789}, 
	4={EMPLOYEE_ID=4, EMPLOYEE_AGE=26, DEPARTMENT_ID=4, EMPLOYEE_NAME=赵六, EMPLOYEE_PASSWORD=1123}, 
	5={EMPLOYEE_ID=5, EMPLOYEE_AGE=27, DEPARTMENT_ID=1, EMPLOYEE_NAME=田七, EMPLOYEE_PASSWORD=123579}
}

  

   4、返回值类型为resultMap:支持手动的封装(多对一)

// 实体类Employee,省略set/get/toString方法
public class Employee {
    private Integer employeeId;
    private String employeeName;
    private String employeePassword;
    private String employeeAge;
    private Department department;
}
// 实体类Department,省略set/get/toString方法
public class Department {
    private Integer departmentId;
    private String description;
    private String location;
} 
// EmployeeMapper接口
public interface EmployeeMapper {
    // 根据用户EmployeeId查询用户信息
    public abstract Employee queryEmployeeByEmployeeId(Integer id);
} 
// EmployeeMapper的全包类路径
<mapper namespace="com.mybatis.dao.EmployeeMapper">
	// 这里的emp可以是任意值,但是一定要和resultMap的值相同
    <resultMap id="emp" type="com.mybatis.entity.Employee">
		// 主键策略
        <id property="employeeId" column="EMPLOYEE_ID" javaType="int" jdbcType="INTEGER"></id>
        <result property="employeeName" column="EMPLOYEE_NAME" javaType="string" jdbcType="VARCHAR"></result>
        <result property="employeePassword" column="EMPLOYEE_PASSWORD" javaType="string" jdbcType="VARCHAR"></result>
        <result property="employeeAge" column="EMPLOYEE_AGE" javaType="string" jdbcType="VARCHAR"></result>
		// 由于Employee实体类中有Department属性,这里的department对应的是属性名,javaType对应Department实体类的全包类路径
        <association property="department" javaType="com.mybatis.entity.Department">
			// 主键策略
            <id property="departmentId" column="DEPARTMENT_ID" javaType="int" jdbcType="INTEGER"></id>
            <result property="description" column="DESCRIPTION" javaType="string" jdbcType="VARCHAR"></result>
            <result property="location" column="LOCATION" javaType="string" jdbcType="VARCHAR"></result>
        </association>
    </resultMap>

    <select id="queryEmployeeByEmployeeId" resultMap="emp">
        SELECT
            e.EMPLOYEE_ID,
            e.EMPLOYEE_NAME,
            e.EMPLOYEE_PASSWORD,
            e.EMPLOYEE_AGE,
            d.DEPARTMENT_ID,
            d.DESCRIPTION,
            d.LOCATION
        FROM employee e
        LEFT JOIN
          department d
        ON
          e.DEPARTMENT_ID=D.DEPARTMENT_ID
        WHERE e. EMPLOYEE_ID=#{employeeId}
    </select>
</mapper>
// 测试结果
Employee{
	employeeId=1, 
	employeeName='张三', 
	employeePassword='123', 
	employeeAge='23', 
	department=Department{departmentId=1, description='市场部', location='北京'}
}

  

   5、返回值类型为resultMap:支持手动的封装(一对多)

// 实体类Employee,省略set/get/toString方法
public class Employee {
    private Integer employeeId;
    private String employeeName;
    private String employeePassword;
    private String employeeAge;
    private Integer departmentId;
}
// 实体类Department,省略set/get/toString方法
public class Department {
    private Integer departmentId;
    private String description;
    private String location;
    private List<Employee> employees;
}
// 查询部门编号为1的所有所有员工信息
public abstract Department queryAllByDepartmentId(Integer id);
<mapper namespace="com.mybatis.dao.DepartmentMapper">
	// dep任意,但是一定要和resultMap的值相同
    <resultMap id="dep" type="com.mybatis.entity.Department">
        <id property="departmentId" column="DEPARTMENT_ID" javaType="int" jdbcType="INTEGER"></id>
        <result property="description" column="DESCRIPTION" javaType="string" jdbcType="VARCHAR"></result>
        <result property="location" column="LOCATION" javaType="string" jdbcType="VARCHAR"></result>
		// Department中有一个List<Employee>集合,使用ofType引入Employee对象
        <collection property="employees" ofType="com.mybatis.entity.Employee">
            <id property="employeeId" column="EMPLOYEE_ID" javaType="int" jdbcType="INTEGER"></id>
            <result property="employeeName" column="EMPLOYEE_NAME" javaType="string" jdbcType="VARCHAR"></result>
            <result property="employeePassword" column="EMPLOYEE_PASSWORD" javaType="string" jdbcType="VARCHAR"></result>
            <result property="employeeAge" column="EMPLOYEE_AGE" javaType="string" jdbcType="VARCHAR"></result>
            <result property="departmentId" column="DEPARTMENT_ID" javaType="int" jdbcType="INTEGER"></result>
        </collection>
    </resultMap>

    <select id="queryAllByDepartmentId" resultMap="dep">
        SELECT
            d.DEPARTMENT_ID,
            d.DESCRIPTION,
            d.LOCATION,
            e.EMPLOYEE_ID,
            e.EMPLOYEE_NAME,
            e.EMPLOYEE_PASSWORD,
            e.EMPLOYEE_AGE
        FROM department d
        LEFT JOIN
          employee e
        ON
          d.DEPARTMENT_ID=e.DEPARTMENT_ID
        WHERE d.DEPARTMENT_ID=#{departmentId}
    </select>
</mapper>
// 测试结果
Department{
	departmentId=1, 
	description='市场部', 
	location='北京', 
	employees=
	[
		Employee{employeeId=1, employeeName='张三', employeePassword='123', employeeAge='23', departmentId=1}, 
		Employee{employeeId=5, employeeName='田七', employeePassword='123579', employeeAge='27', departmentId=1}
	]
}

  

  

posted @ 2020-09-13 20:27  变体精灵  阅读(302)  评论(0编辑  收藏  举报