mybatis参数传递

 使用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按照上述规则把入参转换后的形式来进行解析的。

  一种是通过“#{var}”的形式引用,一种是通过“${var}”的形式引用。前者会被以预编译的方式传递,即会生成PreparedStatement来处理,对应的参数在SQL中会以“?”占位,然后调用PreparedStatement的相应API设值;后者则会先处理好变量再执行相应的SQL,即对应的变量会先被替换,再执行替换变量的SQL。

        1、#是将传入的值当做字符串的形式,eg:select id,name,age from student where id =#{id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id ='1'.

        2、$是将传入的数据直接显示生成sql语句,eg:select id,name,age from student where id =${id},当前端把id值1,传入到后台的时候,就相当于 select id,name,age from student where id = 1.

       3、如果使用在order by 中就需要使用 $

            $将传入的数据直接显示生成在sql中。如:order by ${user_id}, 如果传入的值是id,则解析成的sql为order by id.

       4、能用#的地方不要用$,避免sql注入,安全

采用#{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.model.User" useGeneratedKeys="true"  keyColumn="id"  keyProperty="id">

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

   </insert>

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

  List<User> findByNameAndMobile(@Param("name") String name, String mobile);

   <select id="findByNameAndMobile" resultType="com.model.User">
      select id,name,username,email,mobile from t_user where name=#{name} and mobile=#{1}
   </select>

  第三种,真实传递进来的参数是可以被已经注册了的TypeHandler进行处理的,则直接使用真实传递进来的参数作为真实的变量值。这也是为什么经常可以在Mapper接口里面写delete(Integer id),然后在Mapper语句中直接写#{id}的原因,因为这个时候传递进来的参数是Integer类型,是可以直接被处理的,Mybatis将直接拿它的值作为当前变量id的值,而不会去取传递进来的值的id属性。

   void delete(Long id);

   <insert id="delete" parameterType="java.lang.Long">
      delete t_user where id=#{id}
   </insert>

  采用${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.model.User" parameterType="java.lang.Long" >
      select id,name,username,email,mobile from t_user where id=${_parameter}
   </select>

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

  public interface UserMapper {

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

  }

<select id="findById" resultType="com.learn.mybatis.model.User" parameterType="java.lang.Long" >
      select id,name,username,email,mobile from t_user where id=${id}
</select>

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

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

  <!-- 当对应的接口方法传递多个参数时,可以不指定parameterType参数,就算指定了也没用,因为这个时候默认是Map -->
   <select id="findByNameAndMobile" resultType="com.learn.mybatis.model.User">
      select id,name,username,email,mobile from t_user where name='${name}' and mobile='${1}'
   </select>

   List<User> findByNameAndMobile(@Param("name") String name, String mobile);

  那我们的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条件的内容,比如排序信息,

 

参考文档:

      https://www.cnblogs.com/hellokitty1/p/6007801.html

posted on 2018-08-26 11:27  溪水静幽  阅读(207)  评论(0)    收藏  举报