Mybatis04__输入参数/输出参数
一、输入参数源码分析
1、参数解析器解析参数(得到一个Map集合 names,默认是{arg0,arg1,arg2....},如果某一个位置使用了@Param注解,那么该位置的argx会被注解的值替代)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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....]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // 动态代理执行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、根据操作的类型来选择不同的操作方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | // 通过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命令参数
1 2 3 | public Object convertArgsToSqlCommandParam(Object[] args) { return paramNameResolver.getNamedParams(args); } |
5、获取最终的参数集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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接口
1 2 3 4 | 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这个集合中取取值,例如下面的几种方式都是可以的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!--根据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、测试类
1 2 3 4 5 6 7 8 9 | 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、传入基本类型数据
1 2 3 4 5 6 7 8 | <!--根据主键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类型
1 2 3 4 5 6 7 8 9 | <!--根据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")注解,可以做到见名知意)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!--根据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集合
1 2 3 4 5 6 7 8 9 10 | 传入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类型数组
1 2 3 4 5 6 7 8 9 10 11 12 13 | //根据多个用户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集合
1 2 3 4 5 6 7 8 9 10 11 12 13 | //根据多个用户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中
1 2 | // 根据用户EmployeeId查询用户信息 public abstract Employee queryEmployeeByEmployeeId(Employee employee); |
1 2 3 4 5 6 7 8 9 10 11 12 | <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> |
1 2 | // 测试结果 Employee{employeeId= 3 , employeeName= '王五' , employeePassword= '789' , employeeAge= '25' , departmentId= 3 } |
2、resultType,返回值类型为map
1 2 | // 根据用户EmployeeId查询用户信息 public abstract Map<String,Object> queryEmployeeByEmployeeId(Employee employee); |
1 2 3 4 5 6 7 8 9 10 11 12 | <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> |
1 2 | // 测试结果 {EMPLOYEE_ID= 3 , EMPLOYEE_AGE= 25 , DEPARTMENT_ID= 3 , EMPLOYEE_NAME=王五, EMPLOYEE_PASSWORD= 789 } |
3、如果查询的结果是多条记录,而又指定返回值为map集合,这个时候就不能使用mybatis的自动封装,这个时候要为多条记录指定主键,使用@MapKey("数据库中的列名")
1 2 3 | // EMPLOYEE_ID对应的是数据库的字段 @MapKey ( "EMPLOYEE_ID" ) public abstract Map<String,Object> queryEmployeeByEmployeeId(Employee employee); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // 根据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 2 3 4 5 6 7 8 | // 测试结果 { 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:支持手动的封装(多对一)
1 2 3 4 5 6 7 8 | // 实体类Employee,省略set/get/toString方法 public class Employee { private Integer employeeId; private String employeeName; private String employeePassword; private String employeeAge; private Department department; } |
1 2 3 4 5 6 | // 实体类Department,省略set/get/toString方法 public class Department { private Integer departmentId; private String description; private String location; } |
1 2 3 4 5 | // EmployeeMapper接口 public interface EmployeeMapper { // 根据用户EmployeeId查询用户信息 public abstract Employee queryEmployeeByEmployeeId(Integer id); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | // 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> |
1 2 3 4 5 6 7 8 | // 测试结果 Employee{ employeeId= 1 , employeeName= '张三' , employeePassword= '123' , employeeAge= '23' , department=Department{departmentId= 1 , description= '市场部' , location= '北京' } } |
5、返回值类型为resultMap:支持手动的封装(一对多)
1 2 3 4 5 6 7 8 | // 实体类Employee,省略set/get/toString方法 public class Employee { private Integer employeeId; private String employeeName; private String employeePassword; private String employeeAge; private Integer departmentId; } |
1 2 3 4 5 6 7 | // 实体类Department,省略set/get/toString方法 public class Department { private Integer departmentId; private String description; private String location; private List<Employee> employees; } |
1 2 | // 查询部门编号为1的所有所有员工信息 public abstract Department queryAllByDepartmentId(Integer id); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <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> |
1 2 3 4 5 6 7 8 9 10 11 | // 测试结果 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 } ] } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?