目錄
一、MyBatis的參數處理
從參數的個數來看,可分爲單個參數或者多個參數:
1、單個參數
使用#{參數名}
就能取出參數值。
2、多個參數
MyBatis遇到多個參數會做特殊處理,多個參數會被封裝成一個map,#{}
就是從map中獲取指定的key的值。
所以應該寫成#{key}
的形式來取出map中對應的值,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
這種的話感覺不大方便,因此我們可以使用命名參數。
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不能在表名處使用佔位符。
五、#{}
取值時指定參數的相關規則
#{}
有更豐富的用法,規定參數的一些規則:JavaType
、jdbcTyoe
、mode(存儲過程)
、numericScale
、resultMap
、typeHandler
、jdbcTypeName
、expression(未來準備支持的功能)
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>