1.引入
我們通過上一節課實作了對一個實體對象的基本的增删查改操作,其中,我們在映射檔案和測試的代碼中添加了一下參數。那麼接下來我們就一起來看看MyBatis中參數是如何傳遞的。
2.參數的分類以及參數是傳遞
(1).單個參數:可以接受基本類型,對象類型,集合類型的值。這種情況MyBatis可直接使用這個參數,不需要經過任何處理。
規則:#{參數名/任意名}:取出參數值。
示例:如:
//方法:
public Employee getEmpById(Integer id);
//調用時候參數的傳遞:
EmployeeMapperAnnotation mapper = openSession.getMapper(EmployeeMapperAnnotation.class);
Employee empById = mapper.getEmpById(1);
//參數傳遞給SQL
<select id="getEmpById" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where id = #{id}
</select>
//這裡直接使用:#{參數名/任意名}:取出參數值。
(2).多個參數:
按照我們上面所寫的單個參數,我們照着單個參數的形式嘗試着寫了一下多個參數的形式,發現結果如下:
//方法
public Employee getEmpByIdAndLastName(Integer id,String lastName);
//調用測試:
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpByIdAndLastName(1, "tom");
System.out.println(employee);
//傳遞給的SQL
<select id="getEmpByIdAndLastName" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where id = #{id} and last_name=#{lastName}
</select>
//結果報錯:org.apache.ibatis.binding.BindingException: Parameter 'id' not found.
Available parameters are [1, 0, param1, param2]
通過上面的操作,也就是說MyBatis對于多個參數的傳遞是做了比較特殊的操作,那麼是什麼樣子的特殊操作呢?
任意多個參數,都會被MyBatis重新包裝成一個Map傳入。Map的key是param1,param2,0,1…,值就是參數的值。也就是說:多個參數會被封裝成 一個map,其中
key:param1...paramN,或者參數的索引也可以,即0,1
value:傳入的參數值
#{}就是從map中擷取指定的key的值;但是實際上我們應該擷取的是key所對應的值。
我們進行一下修改:
//方法
public Employee getEmpByIdAndLastName(Integer id,String lastName);
//調用測試:
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpByIdAndLastName(1, "tom");
System.out.println(employee);
//傳遞給的SQL
<select id="getEmpByIdAndLastName" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where id = #{param1} and last_name=#{param2}
</select>
但是,通過這樣的修改以後參數是可以取出來了。但是我們發現當參數比較多的時候,每一個都寫param或者是1,2,3這樣沒有見名知意的效果。MyBatis提供了一個叫做命名參數的來解決這一個問題。(推薦使用)
【命名參數】:明确指定封裝參數時map的key;@Param("id")
多個參數會被封裝成 一個map,
key:使用@Param注解指定的值
value:參數值
#{指定的key}取出對應的參數值
如下:
//方法
public Employee getEmpByIdAndLastName(@Param("id")Integer id,@Param("lastName")String lastName);
//調用測試:
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpByIdAndLastName(1, "tom");
System.out.println(employee);
//傳遞給的SQL
<select id="getEmpByIdAndLastName" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where id = #{id} and last_name=#{lastName}
</select>
(3).傳遞實體對象以及Map傳遞參數
如果多個參數正好是我們業務邏輯的資料模型,我們就可以直接傳入pojo;
規則:#{屬性名}:取出傳入的pojo的屬性值
Map:
如果多個參數不是業務模型中的資料,沒有對應的pojo,不經常使用,為了友善,我們也可以傳入map。
規則: #{key}:取出map中對應的值。
TO:
如果多個參數不是業務模型中的資料,但是經常要使用,推薦來編寫一個TO(Transfer Object)資料傳輸對象
Page{
int index;
int size;
}
如:使用一個map作為多參數傳遞使用
//方法
public Employee getEmpByMap(Map<String, Object> map);
//方法測試
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("id", 2);
map.put("lastName", "Tom");
map.put("tableName", "tbl_employee");
Employee employee = mapper.getEmpByMap(map);
//SQL
<select id="getEmpByMap" resultType="com.atguigu.mybatis.bean.Employee">
select * from ${tableName} where id=${id} and last_name=#{lastName}
</select>
3.如何選擇使用什麼作為參數傳遞:
針對不同的使用場景,應該如何選擇使用的參數?
public Employee getEmp(@Param("id")Integer id,String lastName);
取值:id==>#{id/param1} lastName==>#{param2}
public Employee getEmp(Integer id,@Param("e")Employee emp);
取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}
##特别注意:如果是Collection(List、Set)類型或者是數組,也會特殊處理。也是把傳入的list或者數組封
裝在map中。
key:Collection(collection),如果是List還可以使用這個key(list)
數組(array)
public Employee getEmpById(List<Integer> ids);
取值:取出第一個id的值: #{list[0]}
4.MyBatis是如何處理參數的
MyBatis是如何處理參數的?
總結:參數多時會封裝map,為了不混亂,我們可以使用@Param來指定封裝時使用的key;
#{key}就可以取出map中的值;
(@Param("id")Integer id,@Param("lastName")String lastName);
ParamNameResolver解析參數封裝map的;
//1、names:{0=id, 1=lastName};構造器的時候就确定好了
确定流程:
1.擷取每個标了param注解的參數的@Param的值:id,lastName; 指派給name;
2.每次解析一個參數給map中儲存資訊:(key:參數索引,value:name的值)
name的值:
标注了param注解:注解的值
沒有标注:
1.全局配置:useActualParamName(jdk1.8):name=參數名
2.name=map.size();相當于目前元素的索引
{0=id, 1=lastName,2=2}
args【1,"Tom",'hello'】:
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
//1、參數為null直接傳回
if (args == null || paramCount == 0) {
return null;
//2、如果隻有一個元素,并且沒有Param注解;args[0]:單個參數直接傳回
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
//3、多個元素或者有Param标注
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
//4、周遊names集合;{0=id, 1=lastName,2=2}
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//names集合的value作為key; names集合的key又作為取值的參考args[0]:args【1,"Tom"】:
//eg:{id=args[0]:1,lastName=args[1]:Tom,2=args[2]}
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)param
//額外的将每一個參數也儲存到map中,使用新的key:param1...paramN
//效果:有Param注解可以#{指定的key},或者#{param1}
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
5.參數擷取中#和$的差別
#{}:可以擷取map中的值或者pojo對象屬性的值;
${}:可以擷取map中的值或者pojo對象屬性的值;
select * from tbl_employee where id=${id} and last_name=#{lastName}
Preparing:
select * from tbl_employee where id=2 and last_name=?
差別:
#{}:是以預編譯的形式,将參數設定到sql語句中;PreparedStatement;防止sql注入
${}:取出的值直接拼裝在sql語句中;會有安全問題;
大多情況下,我們取參數的值都應該去使用#{};
原生jdbc不支援占位符的地方我們就可以使用${}進行取值
比如分表、排序。。。;按照年份分表拆分
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}
#{}:更豐富的用法:
規定參數的一些規則:
javaType、 jdbcType、 mode(存儲過程)、 numericScale、
resultMap、 typeHandler、 jdbcTypeName、 expression(未來準備支援的功能);
jdbcType通常需要在某種特定的條件下被設定:
在我們資料為null的時候,有些資料庫可能不能識别mybatis對null的預設處理。比如Oracle(報錯);
JdbcType OTHER:無效的類型;因為mybatis對所有的null都映射的是原生Jdbc的OTHER類型,oracle不能正确處理;
由于全局配置中:jdbcTypeForNull=OTHER;oracle不支援;兩種辦法
1、#{email,jdbcType=OTHER};
2、jdbcTypeForNull=NULL
<setting name="jdbcTypeForNull" value="NULL"/>