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>

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