美文网首页Java
MyBatis笔记 | 详解参数处理(多种类型的参数处理、源码分

MyBatis笔记 | 详解参数处理(多种类型的参数处理、源码分

作者: 一颗白菜_ | 来源:发表于2019-11-28 21:51 被阅读0次

    MyBatis的参数处理

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

    单个参数

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

    多个参数

    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这种的话感觉不大方便,因此我们可以使用命名参数

    命名参数

    明确指定封装参数时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>
    

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

    参数处理

    如果上述的参数中的多个参数正好是我们业务逻辑的数据模型,那我们就可以直接传入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>
    

    相关文章

      网友评论

        本文标题:MyBatis笔记 | 详解参数处理(多种类型的参数处理、源码分

        本文链接:https://www.haomeiwen.com/subject/czuiwctx.html