MyBatis四:MyBatis获取参数的两种方式

MyBatis获取参数的两种方式

一、引入
  1. 从浏览器中发送来的数据,要通过service传输到xxxMapper.xml映射文件中,那么Mapper映射文件要如何来获取传输来的参数,来实现SQL语句的拼接呢。

  2. MyBatis获取参数的两种方式

    ${}:本质是字符串拼接
    
    #{}:本质是填充占位符
    
二、MyBatis获取参数值的各种情况
  1. 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>
    
  2. 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>
    
  3. 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);
        }
    }
    
  4. 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);
        }
    }
    
  5. 命名参数(常用)

    使用@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注解方式传参源码分析
  1. 执行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);
    }
    
    
  2. 执行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);
    }
    
    
  3. #####解析 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);
    }
    
  4. 命名参数的两种参数名的由来
    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;
        }
    }
    
四、小结

上述五种传参的情况,可以归结为两种

  1. 传参是实体类对象
  2. 使用@Param对参数进行注解
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

e_nanxu

感恩每一份鼓励-相逢何必曾相识

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值