zoukankan      html  css  js  c++  java
  • 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}
    	]
    }
    

      

      

  • 相关阅读:
    World file文件格式
    HTML5 基础
    Spring Framework---概况
    Tomcat(1)
    警言妙句
    嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。
    关键字volatile有什么含意?并给出三个不同的例子。
    关键字const有什么含意?
    关于指针数组、数组指针、指针函数、函数指针等的问题
    实现两个int变量的值的交换,要求不使用临时变量。
  • 原文地址:https://www.cnblogs.com/xiaomaomao/p/13661612.html
Copyright © 2011-2022 走看看