MyBatis筆記 | 詳解參數(shù)處理(多種類型的參數(shù)處理、源碼分析、讀取參數(shù)的兩種格式的區(qū)別)

MyBatis的參數(shù)處理

從參數(shù)的個(gè)數(shù)來看,可分為單個(gè)參數(shù)或者多個(gè)參數(shù):

單個(gè)參數(shù)

使用#{參數(shù)名}就能取出參數(shù)值。

多個(gè)參數(shù)

MyBatis遇到多個(gè)參數(shù)會(huì)做特殊處理,多個(gè)參數(shù)會(huì)被封裝成一個(gè)map,#{}就是從map中獲取指定的key的值。
所以應(yīng)該寫成#{key}的形式來取出map中對(duì)應(yīng)的值,map中的key為以下形式:param1、param2、.....。
例如:

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

但是使用param1這種的話感覺不大方便,因此我們可以使用命名參數(shù)。

命名參數(shù)

明確指定封裝參數(shù)時(shí)map的key,在接口方法入?yún)⒅惺褂?code>@Param注解來指定封裝參數(shù)時(shí)map的key。
例如我們使用@Param("id")指定第一個(gè)參數(shù)的key為id,因此我們獲取其參數(shù)值的時(shí)候使用#{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>

從參數(shù)的類型來分類,可以討論如下:

參數(shù)處理

如果上述的參數(shù)中的多個(gè)參數(shù)正好是我們業(yè)務(wù)邏輯的數(shù)據(jù)模型,那我們就可以直接傳入POJP,#{屬性名}就可以取出傳入的POJO的屬性值;如果多個(gè)參數(shù)不是業(yè)務(wù)模型中的數(shù)據(jù),沒有對(duì)應(yīng)的POJO,為了方便,不經(jīng)常使用的話,我們也可以傳入map。此時(shí)#{key}就可以取出map中對(duì)應(yīng)的值。如果多個(gè)參數(shù)不是業(yè)務(wù)模型中的數(shù)據(jù),但是經(jīng)常要使用,推薦來編寫一個(gè)TO(transfer Object)數(shù)據(jù)傳輸對(duì)象。

特別注意的是:如果傳入的參數(shù)是Collection(List、Set)類型或者是數(shù)組時(shí),也會(huì)特殊處理,也就是把傳入的Collection或者數(shù)組封裝到map中:

  • 如果是Collection,則對(duì)應(yīng)的key為collection,特別地,如果是List類型的話,key是list
  • 如果是數(shù)組類型的話,則對(duì)應(yīng)的key為array。

(1)傳入POJO

在接口中定義一個(gè)方法:

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>

測(cè)試方法:

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 {
            //獲取接口的實(shí)現(xiàn)類對(duì)象:會(huì)為接口自動(dòng)的創(chuàng)建一個(gè)代理對(duì)象,代理對(duì)象去執(zhí)行增刪改查
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            //傳入一個(gè)POJO
            Employee emp = new Employee(1,"aaa",null,null);
            //調(diào)用方法
            Employee employee = employeeMapper.getEmpByBean(emp);

            //打印
            System.out.println(employee);
        }finally {
            //關(guān)閉
            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>

測(cè)試方法:

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 {
            //獲取接口的實(shí)現(xiàn)類對(duì)象:會(huì)為接口自動(dòng)的創(chuàng)建一個(gè)代理對(duì)象,代理對(duì)象去執(zhí)行增刪改查
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            //傳入一個(gè)map
            Map<String,Object> map = new HashMap <>();
            map.put("id",1);
            map.put("lastName","aaa");
            //調(diào)用方法
            Employee employee = employeeMapper.getEmpByMap(map);

            //打印
            System.out.println(employee);
        }finally {
            //關(guān)閉
            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>

測(cè)試方法:

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 {
            //獲取接口的實(shí)現(xiàn)類對(duì)象:會(huì)為接口自動(dòng)的創(chuàng)建一個(gè)代理對(duì)象,代理對(duì)象去執(zhí)行增刪改查
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            //傳入一個(gè)map
            Map<String,Object> map = new HashMap <>();
            map.put("id",1);
            map.put("lastName","aaa");
            //調(diào)用方法
            Employee employee = employeeMapper.getEmpByMap(map);

            //打印
            System.out.println(employee);
        }finally {
            //關(guān)閉
            sqlSession.close();
        }
    }
}

從源碼來看參數(shù)處理的過程(即如何封裝Map)

(1) 確定names的值

  • 獲取每個(gè)標(biāo)注了@Param注解的參數(shù)的值,然后把其值賦給name。
  • 每次解析一個(gè)參數(shù),給names這個(gè)map保存信息:(key:參數(shù)索引,key:name的值)。對(duì)于name的值,如果標(biāo)注了Param注解,則name是注解的值;如果沒有標(biāo)注Param注解,如果在全局配置中有配置useActualParamName這個(gè)屬性的值,則name就等于參數(shù)名;如果沒有設(shè)置,則name=map.size():相當(dāng)于當(dāng)前元素的索引。
    對(duì)于我們上述這個(gè)方法,此時(shí)我們的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();
        //傳入的參數(shù)為null則直接返回
        if (args != null && paramCount != 0) {
            //如果只有一個(gè)元素,并且沒有@Param注解。單個(gè)參數(shù)直接返回:args[0]。
            if (!this.hasParamAnnotation && paramCount == 1) {
                return args[(Integer)this.names.firstKey()];
            } else {
                //多個(gè)元素或者有@Param標(biāo)注
                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作為傳入的參數(shù)值的索引。假設(shè)我們傳入的參數(shù)值為:{1,"Tom","0"}。那么value為args[names[key]],即args[0,1,2]:{1,"Tom","0"}。
                    //所以現(xiàn)在param的值為{id=1,lastName="Tom",2="0"}。
                    param.put((String)entry.getValue(), args[(Integer)entry.getKey()]);
                    //額外的將每一個(gè)參數(shù)也保存到map中,使用新的key:param1....paramN
                     //所以現(xiàn)在的話,如果有使用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;
        }
    }

詳細(xì)過程看上述源碼中寫的注釋。

總結(jié)

參數(shù)多時(shí)會(huì)封裝成map,為了不混亂,我們可以使用@Param來指定封裝時(shí)使用的key,這樣就可以通過#{指定的值}來取出map中的值,否則應(yīng)該使用#{param+數(shù)字}來獲取。


參數(shù)處理中#{}${}的區(qū)別

使用 #{}${}都可以取出map和pojo中的值,但是兩者還是有區(qū)別的。

  • #{}:是以預(yù)編譯的形式,將參數(shù)設(shè)置到sql語句中??梢苑乐箂ql注入。
  • ${}:取出的值直接拼裝在sql語句中。會(huì)有安全問題。

例如我們配置如下:

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

假設(shè)我們傳入的參數(shù)為id=1,last_name="Tom",其執(zhí)行的sql語句是這樣的:select * from tb1_employee where id = 2 and last_name = ?

大多數(shù)情況下,我們?nèi)?shù)的值都應(yīng)該使用#{},但是如果原生jdbc不支持占位符的地方我們就可以使用${}進(jìn)行取值。
例如分表、排序等等:
按照年份分表拆分:select * from ${year}_salary where ....然后通過傳入年份進(jìn)行拼接就可以實(shí)現(xiàn)。
傳入排序規(guī)則來排序:select * from tb1_employee order by ${f_name} ${order}。

示例:使用${}來使表名可以動(dòng)態(tài)指定
sql映射文件部分代碼如下:

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

測(cè)試方法:

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 {
            //獲取接口的實(shí)現(xiàn)類對(duì)象:會(huì)為接口自動(dòng)的創(chuàng)建一個(gè)代理對(duì)象,代理對(duì)象去執(zhí)行增刪改查
            EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
            //傳入一個(gè)map
            Map<String,Object> map = new HashMap <>();
            map.put("id",1);
            map.put("lastName","aaa");
            map.put("tableName","tb1_employee");
            //調(diào)用方法
            Employee employee = employeeMapper.getEmpByMap(map);

            //打印
            System.out.println(employee);
        }finally {
            //關(guān)閉
            sqlSession.close();
        }
    }
}

此時(shí)就能通過傳入一個(gè)表名的參數(shù)來動(dòng)態(tài)指定sql中的表名,但是如果指定表名那處改為#{tableName}則會(huì)報(bào)錯(cuò),因?yàn)閟ql不能在表名處使用占位符。

#{}取值時(shí)指定參數(shù)的相關(guān)規(guī)則

#{}有更豐富的用法,規(guī)定參數(shù)的一些規(guī)則:JavaType、jdbcTyoemode(存儲(chǔ)過程)、numericScaleresultMap、typeHandler、jdbcTypeNameexpression(未來準(zhǔn)備支持的功能)

jdbcType

通常需要在某種特定的條件下被設(shè)置,在我們數(shù)據(jù)為null的時(shí)候,有些數(shù)據(jù)庫(kù)可能不能設(shè)備mybatis對(duì)null的默認(rèn)處理,比如Oracle(數(shù)據(jù)為null時(shí)會(huì)報(bào)錯(cuò))。
當(dāng)使用的是Oracle數(shù)據(jù)庫(kù)時(shí),如果傳入的參數(shù)值有一個(gè)null值的話,會(huì)報(bào)錯(cuò):JdbcType OTHER:無效的類型。因?yàn)閙ybatis對(duì)所有的null都映射的是原生Jdbc的OTHER類型,Oracle不能正確處理。此時(shí)有兩種解決方法:

  • 我們可以使用jdbcType來指定類型,例如#{email,jdbcType=NULL}
  • 在全局配置文件中設(shè)置jdbcTypeForNull屬性為NULL。
<settings>
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容