MyBatis获取参数的两种方式
一、引入
-
从浏览器中发送来的数据,要通过service传输到xxxMapper.xml映射文件中,那么Mapper映射文件要如何来获取传输来的参数,来实现SQL语句的拼接呢。
-
MyBatis获取参数的两种方式
${}:本质是字符串拼接 #{}:本质是填充占位符
二、MyBatis获取参数值的各种情况
-
mapper接口方法的参数为单个的字面量类型
可以通过KaTeX parse error: Expected 'EOF', got '#' at position 5: { }和#̲{ }**,在**大括号内写入…{ }的单引号问题
public interface ParamMapper { // 根据用户名查询人员信息 User getUserByName(String username); } // 注意:真实代码中,两个方式只能写一种 <mapper namespace="com.atguigu.mybatis.mapper.ParamMapper"> <select id="getUserByName" resultType="User"> // 方式一 select * from t_user where username = #{username} // 方式二 select * from t_user where username = '${username}' </select> </mapper>
-
mapper接口有多个参数
此时在框架底层,MyBatis会将这些参数放在一个map集合中,以下述两种方式进行存储
方式1:以arg0,arg1,…为键,以参数为值
方式2:以param1,param2,…为键,以参数为值
故,在#{ }和${ }内放入的是上述两种键名称,不可以是其它的
<mapper namespace="com.atguigu.mybatis.mapper.ParamMapper"> <select id="checkLogin" resultType="user"> select * from t_user where username = #{arg0} and pwd = #{arg1} </select> </mapper>
-
mapper接口有多个参数
可以手动将这些参数放在一个map中存储,然后将map中的键放在#{ }内
mapper接口
public interface ParamMapper { User checkLoginByMap(Map<String,Object> map); }
映射文件
<mapper namespace="com.atguigu.mybatis.mapper.ParamMapper"> <select id="checkLoginByMap" resultType="user"> select * from t_user where username = #{username} and pwd = #{password} </select> </mapper>
测试程序
public class ParamTest { @Test public void test4() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); ParamMapper mapper = sqlSession.getMapper(ParamMapper.class); Map<String, Object> map = new HashMap<>(); map.put("username","李四"); map.put("password","123456"); User userByName = mapper.checkLoginByMap(map); System.out.println("userByName = " + userByName); } }
-
mapper接口参数是一个实体类对象
只需要通过#{ } 和 ${ },以属性发的方式访问属性值即可。
mapper接口
public interface ParamMapper { int insertUser(User user); }
mapper映射文件
<insert id="insertUser"> insert into t_user value (null,#{username},#{pwd},#{age},#{sex},#{email}) </insert>
测试类
public class ParamTest { @Test public void test4() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); ParamMapper mapper = sqlSession.getMapper(ParamMapper.class); int i = mapper.insertUser(new User(null, "张三", "123456", 23, '女', "zhangsan@qq.com")); System.out.println("i = " + i); } }
-
命名参数(常用)
使用@Param注解来命名各参数,MyBatis会自动将@Param注解中的值存入到参数map中,即我们可以通过以下两种方式获取传来的参数值
① 各@Param注解中的值
② parm1, param2 …
mapper接口
public interface ParamMapper { User checkLoginByParam(@Param("username") String username, @Param("password") String pwd); }
mapper映射文件
<insert id="insertUser"> select * from t_user where username = #{username} and pwd = #{password} </insert>
测试类
public class ParamTest { @Test public void test6() { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); ParamMapper mapper = sqlSession.getMapper(ParamMapper.class); User userByName = mapper.checkLoginByParam("李四","123456"); System.out.println("userByName = " + userByName); } }
三、@Param注解方式传参源码分析
-
执行invoke调用方法
在测试类中,getMapper方法的底层使用的是代理模式
@Override public Object invoke(Object proxy, Method method, Object[] args) { try { // 利用反射,调用invoke方法 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } } @Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) { return mapperMethod.execute(sqlSession, args); }
-
执行execute,判断sql语句类型
public Object execute(SqlSession sqlSession, Object[] args) { Object result; // 1.command:命令,包含mapper接口的指定方法全类名和sql类型 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; } // 2.判断select类型语句的返回值类型 case SELECT: // 2.1 如果返回值为空 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; // 2.2 如果返回多条值 } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); // 2.3 如果返回map集合 } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); // 2.4 如果返回cursor游标 } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); // 2.5 其它返回值 } else { // 将方法的参数转换为sqlCommand参数 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; } // 2.5 public Object convertArgsToSqlCommandParam(Object[] args) { return paramNameResolver.getNamedParams(args); }
-
#####解析 mapper接口中被调用方法的参数
public ParamNameResolver(Configuration config, Method method) { this.useActualParamName = config.isUseActualParamName(); // 1.利用反射,获取方法的参数类型数组 final Class<?>[] paramTypes = method.getParameterTypes(); /* 2.获取参数的注解,参数有多个,每个参数的注解也有多个,故返回的是一个二维数组 第一维:参数 第二维:参数的注解 */ final Annotation[][] paramAnnotations = method.getParameterAnnotations(); final SortedMap<Integer, String> map = new TreeMap<>(); // 3.参数注解二维数组的第一维的长度 int paramCount = paramAnnotations.length; // 4.循环每一个参数,并添加到map中 for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) { if (isSpecialParameter(paramTypes[paramIndex])) { continue; } String name = null; // 4.1 获取每一个参数的名字 for (Annotation annotation : paramAnnotations[paramIndex]) { // 4.2 如果该参数的注解类型是Param, if (annotation instanceof Param) { hasParamAnnotation = true; // 4.3 则获取该参数的注解属性值,赋值给name name = ((Param) annotation).value(); // 4.4 只要有一个参数的注解类型是Param,就跳出for循环 break; } } if (name == null) { // @Param was not specified. if (useActualParamName) { name = getActualParamName(method, paramIndex); } if (name == null) { // use the parameter index as the name ("0", "1", ...) // gcode issue #71 name = String.valueOf(map.size()); } // 4.5 将参数索引为键(从0开始),该参数注解为Param的参数名为值,添加到map集合中 map.put(paramIndex, name); } // 5.将map转型后赋值给names names = Collections.unmodifiableSortedMap(map); }
-
命名参数的两种参数名的由来
public Object getNamedParams(Object[] args) { final int paramCount = names.size(); if (args == null || paramCount == 0) { return null; } else if (!hasParamAnnotation && paramCount == 1) { Object value = args[names.firstKey()]; return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null); // 1.如果有Param注解的参数 } else { final Map<String, Object> param = new ParamMap<>(); int i = 0; // 2.将names集合中的每一对键值对处理后放入param集合 for (Map.Entry<Integer, String> entry : names.entrySet()) { // 2.1 param集合中:键为names集合的值(即参数名),值为args[](即参数中的值) param.put(entry.getValue(), args[entry.getKey()]); // 2.2 设置参数名:param+索引 final String genericParamName = GENERIC_NAME_PREFIX + (i + 1); /* 2.3 如果names中不包含上一条设置的参数名,则将该值添加到param集合中 这就是为什么mybatis在获取有@Param注解的参数时,传入#{}中的可以是@Param中的参数 名,也可以是param1,param2... */ if (!names.containsValue(genericParamName)) { param.put(genericParamName, args[entry.getKey()]); } i++; } return param; } }
四、小结
上述五种传参的情况,可以归结为两种
- 传参是实体类对象
- 使用@Param对参数进行注解