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、jdbcTyoe、mode(存儲(chǔ)過程)、numericScale、resultMap、typeHandler、jdbcTypeName、expression(未來準(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>