MyBatis笔记 | 详解参数处理(多种类型的参数处理、源码分析、读取参数的两种格式的区别)...

目录

一、MyBatis的参数处理

1、单个参数

2、多个参数

3、命名参数

4、参数处理

(1)传入POJO

(2)传入Map

(3)传入List

二、从源码来看参数处理的过程(即如何封装Map)

1、 确定names的值

2、封装Map

四、参数处理中#{}与${}的区别

五、#{}取值时指定参数的相关规则


一、MyBatis的参数处理

从参数的个数来看,可分为单个参数或者多个参数:

1、单个参数

使用#{参数名}就能取出参数值。

2、多个参数

MyBatis遇到多个参数会做特殊处理,多个参数会被封装成一个map,#{}就是从map中获取指定的key的值。
所以应该写成#{key}的形式来取出map中对应的值,map中的key为以下形式:param1param2.....
例如:

<select id="getEmpByIdAndLastName" resultType="com.cerr.mybatis.Employee">
        select * from tb1_employee where id = #{param1} and last_name = #{param2}
</select>

但是使用param1这种的话感觉不大方便,因此我们可以使用命名参数

3、命名参数

明确指定封装参数时map的key,在接口方法入参中使用@Param注解来指定封装参数时map的key。
例如我们使用@Param("id")指定第一个参数的key为id,因此我们获取其参数值的时候使用#{id}即可。

public Employee getEmpByIdAndLastName(@Param("id") Integer id,
         @Param("lastName") String lastName);

所以sql映射文件中的部分sql代码我们可以修改如下:

    <select id="getEmpByIdAndLastName" resultType="com.cerr.mybatis.Employee">
        select * from tb1_employee where id = #{id} and last_name = #{lastName}
    </select>

从参数的类型来分类,可以讨论如下:

4、参数处理

如果上述的参数中的多个参数正好是我们业务逻辑的数据模型,那我们就可以直接传入POJP,#{属性名}就可以取出传入的POJO的属性值;如果多个参数不是业务模型中的数据,没有对应的POJO,为了方便,不经常使用的话,我们也可以传入map。此时#{key}就可以取出map中对应的值。如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(transfer Object)数据传输对象。

特别注意的是:如果传入的参数是Collection(List、Set)类型或者是数组时,也会特殊处理,也就是把传入的Collection或者数组封装到map中:

  • 如果是Collection,则对应的key为collection,特别地,如果是List类型的话,key是list
  • 如果是数组类型的话,则对应的key为array

(1)传入POJO

在接口中定义一个方法:

public Employee getEmpByBean(Employee employee);

然后在sql映射文件中配置如下:

<select id="getEmpByBean" resultType="com.cerr.mybatis.Employee">
        select * from tb1_employee where id = #{id} and last_name = #{lastName}
</select>

测试方法:

package com.cerr.mybatis;

import com.cerr.mybatis.dao.EmployeeMapper;
import com.cerr.mybatis.dao.EmployeeMapperAnnotation;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;

public class MyBatisTest {

    //获取SQLSessionFactory
    public SqlSessionFactory getSqlSessionFactory() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        return new SqlSessionFactoryBuilder().build(inputStream);
    }
    @Test
    public void test8() throws IOException {
        //获取SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        //获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            //获取接口的实现类对象:会为接口自动的创建一个代理对象,代理对象去执行增删改查
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            //传入一个POJO
            Employee emp = new Employee(1,"aaa",null,null);
            //调用方法
            Employee employee = employeeMapper.getEmpByBean(emp);

            //打印
            System.out.println(employee);
        }finally {
            //关闭
            sqlSession.close();
        }
    }
}

(2)传入Map

在接口中定义方法:

public Employee getEmpByMap(Map<String,Object> map);

在sql映射文件中配置如下:

<select id="getEmpByMap" resultType="com.cerr.mybatis.Employee">
        select * from tb1_employee where id = #{id} and last_name = #{lastName}
</select>

测试方法:

package com.cerr.mybatis;

import com.cerr.mybatis.dao.EmployeeMapper;
import com.cerr.mybatis.dao.EmployeeMapperAnnotation;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;

public class MyBatisTest {

    //获取SQLSessionFactory
    public SqlSessionFactory getSqlSessionFactory() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        return new SqlSessionFactoryBuilder().build(inputStream);
    }
    @Test
    public void test7() throws IOException {
        //获取SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        //获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            //获取接口的实现类对象:会为接口自动的创建一个代理对象,代理对象去执行增删改查
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            //传入一个map
            Map<String,Object> map = new HashMap <>();
            map.put("id",1);
            map.put("lastName","aaa");
            //调用方法
            Employee employee = employeeMapper.getEmpByMap(map);

            //打印
            System.out.println(employee);
        }finally {
            //关闭
            sqlSession.close();
        }
    }
}

(3)传入List

在接口中定义方法:

public Employee getEmpByIdList(List<Integer> id);

在sql映射文件中配置:

<select id="getEmpByIdList" resultType="com.cerr.mybatis.Employee">
        select * from tb1_employee where id = #{list[0]}
</select>

测试方法:

package com.cerr.mybatis;
import com.cerr.mybatis.dao.EmployeeMapper;
import com.cerr.mybatis.dao.EmployeeMapperAnnotation;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.*;
public class MyBatisTest {

    //获取SQLSessionFactory
    public SqlSessionFactory getSqlSessionFactory() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        return new SqlSessionFactoryBuilder().build(inputStream);
    }
    @Test
    public void test7() throws IOException {
        //获取SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        //获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            //获取接口的实现类对象:会为接口自动的创建一个代理对象,代理对象去执行增删改查
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            //传入一个map
            Map<String,Object> map = new HashMap <>();
            map.put("id",1);
            map.put("lastName","aaa");
            //调用方法
            Employee employee = employeeMapper.getEmpByMap(map);

            //打印
            System.out.println(employee);
        }finally {
            //关闭
            sqlSession.close();
        }
    }
}

二、从源码来看参数处理的过程(即如何封装Map)

1、 确定names的值

  • 获取每个标注了@Param注解的参数的值,然后把其值赋给name。
  • 每次解析一个参数,给names这个map保存信息:(key:参数索引,key:name的值)。对于name的值,如果标注了Param注解,则name是注解的值;如果没有标注Param注解,如果在全局配置中有配置useActualParamName这个属性的值,则name就等于参数名;如果没有设置,则name=map.size():相当于当前元素的索引。
    对于我们上述这个方法,此时我们的names为{0=id,1=lastName,2=2}:
public Employee getEmpByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName,String gender);

2、封装Map

    public Object getNamedParams(Object[] args) {
        int paramCount = this.names.size();
        //传入的参数为null则直接返回
        if (args != null && paramCount != 0) {
            //如果只有一个元素,并且没有@Param注解。单个参数直接返回:args[0]。
            if (!this.hasParamAnnotation && paramCount == 1) {
                return args[(Integer)this.names.firstKey()];
            } else {
                //多个元素或者有@Param标注
                Map<String, Object> param = new ParamMap();
                int i = 0;
                //遍历names集合。
                for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
                    Entry<Integer, String> entry = (Entry)var5.next();
                    //将names集合的value作为key,names集合的key作为传入的参数值的索引。假设我们传入的参数值为:{1,"Tom","0"}。那么value为args[names[key]],即args[0,1,2]:{1,"Tom","0"}。
                    //所以现在param的值为{id=1,lastName="Tom",2="0"}。
                    param.put((String)entry.getValue(), args[(Integer)entry.getKey()]);
                    //额外的将每一个参数也保存到map中,使用新的key:param1....paramN
                     //所以现在的话,如果有使用Param注解的话可以使用#{指定的key},或者#{param1}
                    String genericParamName = "param" + String.valueOf(i + 1);
                    if (!this.names.containsValue(genericParamName)) {
                        param.put(genericParamName, args[(Integer)entry.getKey()]);
                    }
                }

                return param;
            }
        } else {
            return null;
        }
    }

详细过程看上述源码中写的注释。

 

参数多时会封装成map,为了不混乱,我们可以使用@Param来指定封装时使用的key,这样就可以通过#{指定的值}来取出map中的值,否则应该使用#{param+数字}来获取。


四、参数处理中#{}${}的区别

使用 #{}${}都可以取出map和pojo中的值,但是两者还是有区别的。

  • #{}:是以预编译的形式,将参数设置到sql语句中。可以防止sql注入。
  • ${}:取出的值直接拼装在sql语句中。会有安全问题。

例如我们配置如下:

<select id="getEmpByMap" resultType="com.cerr.mybatis.Employee">
        select * from tb1_employee where id = ${id} and last_name = #{lastName}
</select>

假设我们传入的参数为id=1,last_name="Tom",其执行的sql语句是这样的:select * from tb1_employee where id = 2 and last_name = ?

大多数情况下,我们取参数的值都应该使用#{},但是如果原生jdbc不支持占位符的地方我们就可以使用${}进行取值。
例如分表、排序等等:
按照年份分表拆分:select * from ${year}_salary where ....然后通过传入年份进行拼接就可以实现。
传入排序规则来排序:select * from tb1_employee order by ${f_name} ${order}

示例:使用${}来使表名可以动态指定
sql映射文件部分代码如下:

    <select id="getEmpByMap" resultType="com.cerr.mybatis.Employee">
        select * from ${tableName} where id = #{id} and last_name = #{lastName}
    </select>

测试方法:

package com.cerr.mybatis;

import com.cerr.mybatis.dao.EmployeeMapper;
import com.cerr.mybatis.dao.EmployeeMapperAnnotation;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.junit.Assert.*;

public class MyBatisTest {

    //获取SQLSessionFactory
    public SqlSessionFactory getSqlSessionFactory() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        return new SqlSessionFactoryBuilder().build(inputStream);
    }
    @Test
    public void test7() throws IOException {
        //获取SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        //获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            //获取接口的实现类对象:会为接口自动的创建一个代理对象,代理对象去执行增删改查
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            //传入一个map
            Map<String,Object> map = new HashMap <>();
            map.put("id",1);
            map.put("lastName","aaa");
            map.put("tableName","tb1_employee");
            //调用方法
            Employee employee = employeeMapper.getEmpByMap(map);

            //打印
            System.out.println(employee);
        }finally {
            //关闭
            sqlSession.close();
        }
    }
}

此时就能通过传入一个表名的参数来动态指定sql中的表名,但是如果指定表名那处改为#{tableName}则会报错,因为sql不能在表名处使用占位符。

五、#{}取值时指定参数的相关规则

#{}有更丰富的用法,规定参数的一些规则:JavaTypejdbcTyoemode(存储过程)numericScaleresultMaptypeHandlerjdbcTypeNameexpression(未来准备支持的功能)

jdbcType

通常需要在某种特定的条件下被设置,在我们数据为null的时候,有些数据库可能不能设备mybatis对null的默认处理,比如Oracle(数据为null时会报错)。
当使用的是Oracle数据库时,如果传入的参数值有一个null值的话,会报错:JdbcType OTHER:无效的类型。因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,Oracle不能正确处理。此时有两种解决方法:

  • 我们可以使用jdbcType来指定类型,例如#{email,jdbcType=NULL}
  • 在全局配置文件中设置jdbcTypeForNull属性为NULL
<settings>
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章