Mybatis介绍之参数传递

Stella981
• 阅读 838

转载自 :https://blog.csdn.net/u010643307/article/details/70148702

1     使用Mapper接口时参数传递方式

       Mybatis在使用Mapper接口进行编程时,其实底层是采用了动态代理机制,表面上是调用的Mapper接口,而实际上是通过动态代理调用的SqlSession的对应方法,如selectOne(),有兴趣的朋友可以查看DefaultSqlSession的getMapper()方法实现,其最终会获得一个代理了Mapper接口的MapperProxy对象。MapperProxy对象在调用Mapper接口方法时会把传递的参数做一个转换,然后把转换后的参数作为入参调用SqlSession对应的操作方法(如selectOne、insert等)。转换过程可以参考MapperMethod的execute()方法实现。简单来说是以下规则:

1、如果传递过来是单参数,且没有以@Param注解进行命名,则直接将单参数作为真实的参数调用SqlSession的对应方法。

2、如果传递过来的不是单参数或者是包含以@Param注解进行命名的参数,则会将对应的参数转换为一个Map进行传递。具体规则如下:

2.1、      会把对应的参数按照顺序以param1、param2、paramN这样的形式作为Key存入目标Map中,第一个参数是param1,第N个参数是paramN。

2.2、      如果参数是以@Param注解命名的参数,则以@Param指定的名称作为Key存入目标Map中。

2.3、      如果参数不是以@Param注解命名的,则按照顺序以0、1、N这样的形式作为Key存入目标Map中,第一个参数是0,第N个参数是N。

       我们在Mapper.xml中定义SQL语句中用到的变量最终是以Mybatis按照上述规则把入参转换后的形式来进行解析的。

2     参数引用#与$的区别

       在Mybatis的Mapper.xml文件中为了构建动态的SQL,我们通常需要通过引用传递的参数来达到对应的效果。在引用传递的参数的时候有两种方式,一种是通过“#{var}”的形式引用,一种是通过“${var}”的形式引用。前者会被以预编译的方式传递,即会生成PreparedStatement来处理,对应的参数在SQL中会以“?”占位,然后调用PreparedStatement的相应API设值;而后者则会先处理好变量再执行相应的SQL,即对应的变量会先被替换,再执行替换了变量的SQL。在一个Mapper映射语句中我们可以同时使用“#”和“$”处理变量。

3     采用#{var}的形式处理变量

       采用#{var}的形式来引用变量时,其中的变量会在解析Mapper.xml文件中的语句时就被替换为占位符“?”,同时通过ParameterMapping类记录对应的变量信息。在真正执行对应的语句时会用传递的真实参数根据ParameterMapping信息给PreparedStatement设置参数,具体可参考PreparedStatementHandler的parameterize()方法实现。对应的变量在解析的时候都是以传递进来的参数为基础来解析的,根据前面的介绍我们知道,传递进来的参数只有两种情况,一种是原始类型的单参数,一种是被Mybatis封装过的Map。那对应的参数Mybatis的如何解析的呢?有三种情况。

       第一种,如果真实传递进来的是原始类型,那么对应的属性解析就是就是基于原始类型的属性。比如我们真是传递进来的一个User类型的对象,User对象有id、name等属性,那么我们在Mapper语句中采用#{var}形式访问变量的时候就可以使用#{id}和#{name}。这跟使用${var}形式访问变量是不同的,使用${var}形式时我们需要使用${_parameter.id}和${_parameter.name}。

   <insert id=_"insert"_parameterType=_"com.elim.learn.mybatis.model.User"_useGeneratedKeys=_"true"_ keyColumn=_"id"_ keyProperty=_"id"_>

      insert into t_user(name,username,email,mobile) values(#{name},#{username},#{email},#{mobile})

   

       第二种,如果真实传递进来的是一个Map,那么对应的变量则可以是这个Map里面的Key。比如下面示例中我们的接口方法对应的是两个参数,那最终到执行Mapper语句时会被封装成一个Map,Map里面的Key包含name、1、param1和param2,那么我们在使用的时候就可以使用这些变量。

   List findByNameAndMobile(@Param("name") String name, Stringmobile);

   <select id=_"findByNameAndMobile"_resultType=_"com.elim.learn.mybatis.model.User"_>

      select id,name,username,email,mobile from t_user where name=#{name} and mobile=#{1}

   

       第三种,真实传递进来的参数是可以被已经注册了的TypeHandler进行处理的,则直接使用真实传递进来的参数作为真实的变量值。这也是为什么我们经常可以在Mapper接口里面写delete(Integer id),然后在Mapper语句中直接写#{id}的原因,因为这个时候传递进来的参数是Integer类型,是可以直接被处理的,Mybatis将直接拿它的值作为当前变量id的值,而不会去取传递进来的值的id属性。换句话说,这个时候我们在Mapper语句中定义的变量可以随便命名,随便怎么命名都可以被Mybatis正确的处理。但是通常我们会把它命名为与接口方法参数名称一致,方便阅读。

   void delete(Long id);

   <insert id=_"delete"_ parameterType=_"java.lang.Long"_>

      delete t_user where id=#{id}

   

       关于预编译的参数处理是由DefaultParameterHandler类的setParameters()方法处理,核心代码如下,欲了解更多,可以参考DefaultParameterHandler的完整代码。

  public void setParameters(PreparedStatement ps) {

    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());

    List parameterMappings =boundSql.getParameterMappings();

    if (parameterMappings != null) {

      for (inti = 0; i < parameterMappings.size(); i++) {

        ParameterMapping parameterMapping = parameterMappings.get(i);

        if (parameterMapping.getMode() != ParameterMode.OUT) {

          Object value;

          String propertyName = parameterMapping.getProperty();

          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params

            value = boundSql.getAdditionalParameter(propertyName);

          } else if (parameterObject == null) {

            value = null;

          } else if(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {

            value = parameterObject;

          } else {

            MetaObject metaObject =configuration.newMetaObject(parameterObject);

            value = metaObject.getValue(propertyName);

          }

          TypeHandler typeHandler = parameterMapping.getTypeHandler();

          JdbcType jdbcType = parameterMapping.getJdbcType();

          if (value == null && jdbcType == null) {

            jdbcType = configuration.getJdbcTypeForNull();

          }

          try {

            typeHandler.setParameter(ps, i + 1, value, jdbcType);

          } catch (TypeException e) {

            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);

          } catch (SQLException e) {

            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);

          }

        }

      }

    }

  }

4     采用${var}的形式处理变量

       采用“${var}”的形式来引用变量时,其中的变量会在MappedStatement调用getBoundSql()方法获取对应的BoundSql时被替换。${var}中的var可以是如下取值:

1、内置的_parameter变量,对应转换后的传递参数,在只传递单参数且是没有使用@Param注解对参数命名的时候如果我们需要通过${var}的形式来访问传递的单参数,则可以使用${_parameter};

2、如果对应的Mapper接口方法是多参数或者拥有@Param命名的参数时可以使用param1、paramN的形式;

3、如果对应的Mapper接口方法参数是@Param命名的方法参数,则可以使用@Param指定的名称;

4、如果对应的Mapper接口方法拥有多个参数,且拥有没有使用@Param命名的参数,那没有使用@Param命名的参数可以通过0、1、N形式访问。

       根据上述规则如我们有一个findById的方法其接收一个Long类型的参数作为ID,当使用${var}的形式引用变量时则可以写成如下这样:

   <select id=_"findById"_resultType=_"com.elim.learn.mybatis.model.User"_parameterType=_"java.lang.Long"_ >

      select id,name,username,email,mobile from t_user where id=${_parameter}

   

       当我们的Mapper接口方法参数使用了@Param命名的时候,我们还可以使用@Param指定的参数名。

public interface UserMapper {

   User findById(@Param("id") Long id);

}

   <select id=_"findById"_resultType=_"com.elim.learn.mybatis.model.User"_parameterType=_"java.lang.Long"_ >

      select id,name,username,email,mobile from t_user where id=${id}

   

       注意,但是使用了@Param对单参数命名后我们就不能再在Mapper语句中通过${_parameter}来引用接口方法参数传递过来的单参数了,因为此时其已经被包装为一个Map了,如果要通过_parameter来引用的话,我们应该使用${_parameter.param1}或${_parameter.varName},对于上面的示例来说就是${_parameter.param1}或${_parameter.id}。

       下面我们来看一个传递多参数的,假设我们有如下这样的一个Mapper语句及对应的Mapper接口方法,这个Mapper接口方法接收两个参数,第一个参数是用@Param注解指定了参数名为name,第二个参数是没有使用注解的,具体如下。

   

   <select id=_"findByNameAndMobile"_resultType=_"com.elim.learn.mybatis.model.User"_>

      select id,name,username,email,mobile from t_user where name='${name}' and mobile='${1}'

   

   List findByNameAndMobile(@Param("name") String name, Stringmobile);

     那我们的Mapper.xml文件中的对应语句需要Mapper接口方法参数时有哪几种方式呢?按照之前的规则,对于第一个方法参数name而言,因为使用了@Param指定了参数名name,所以我们可以在Mapper.xml文件中通过变量name和param1来引用它,而第二个参数mobile是没有使用@Param指定参数名的,则我们可以使用param2和参数相对位置“1”来引用它。如上面的示例中,我们就是通过第二个参数的相对位置“1”来引用的。如果我们把第二个参数改为${mobile}是引用不到,且系统会报如下错误,有兴趣的朋友可以试一下。

org.apache.ibatis.binding.BindingException: Parameter 'mobile' not found. Available parameters are [1, name, param1, param2]

       一般情况下为了防止SQL注入问题,是不建议直接在where条件中使用${var}的形式访问变量的,通常会用预编译形式的#{var}。而${var}往往是用来传递一些非where条件的内容,比如排序信息,具体用法请根据实际情况决定。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这