之前見到的所有語句都使用了簡單的參數(shù)形式。但實(shí)際上,參數(shù)是 MyBatis 非常強(qiáng)大的元素。對于大多數(shù)簡單的使用場景,你都不需要使用復(fù)雜的參數(shù),比如:
<select id="selectUsers" resultType="User">
select id, username, password
from users
where id = #{id}
</select>
上面的這個(gè)示例說明了一個(gè)非常簡單的命名參數(shù)映射。鑒于參數(shù)類型(?parameterType
?)會被自動(dòng)設(shè)置為 int,這個(gè)參數(shù)可以隨意命名。原始類型或簡單數(shù)據(jù)類型(比如 Integer 和 String)因?yàn)闆]有其它屬性,會用它們的值來作為參數(shù)。 然而,如果傳入一個(gè)復(fù)雜的對象,行為就會有點(diǎn)不一樣了。比如:
<insert id="insertUser" parameterType="User">
insert into users (id, username, password)
values (#{id}, #{username}, #{password})
</insert>
如果 User 類型的參數(shù)對象傳遞到了語句中,會查找 ?id
?、?username
?和 ?password
?屬性,然后將它們的值傳入預(yù)處理語句的參數(shù)中。
對傳遞語句參數(shù)來說,這種方式真是干脆利落。不過參數(shù)映射的功能遠(yuǎn)不止于此。
首先,和 MyBatis 的其它部分一樣,參數(shù)也可以指定一個(gè)特殊的數(shù)據(jù)類型。
#{property,javaType=int,jdbcType=NUMERIC}
和 MyBatis 的其它部分一樣,幾乎總是可以根據(jù)參數(shù)對象的類型確定 ?javaType
?,除非該對象是一個(gè) ?HashMap
?。這個(gè)時(shí)候,你需要顯式指定 ?javaType
?來確保正確的類型處理器(?TypeHandler
?)被使用。
?JDBC
?要求,如果一個(gè)列允許使用 ?null
?值,并且會使用值為 ?null
?的參數(shù),就必須要指定 ?JDBC
?類型(?jdbcType
?)。
要更進(jìn)一步地自定義類型處理方式,可以指定一個(gè)特殊的類型處理器類(或別名),比如:
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
參數(shù)的配置好像越來越繁瑣了,但實(shí)際上,很少需要如此繁瑣的配置。
對于數(shù)值類型,還可以設(shè)置 ?numericScale
?指定小數(shù)點(diǎn)后保留的位數(shù)。
#{height,javaType=double,jdbcType=NUMERIC,numericScale=2}
最后,?mode
?屬性允許你指定 ?IN
?,?OUT
?或 ?INOUT
?參數(shù)。如果參數(shù)的 ?mode
?為 ?OUT
?或 ?INOUT
?,將會修改參數(shù)對象的屬性值,以便作為輸出參數(shù)返回。 如果 ?mode
?為 ?OUT
?(或 ?INOUT
?),而且 ?jdbcType
?為 ?CURSOR
?(也就是 ?Oracle
?的 ?REFCURSOR
?),你必須指定一個(gè) ?resultMap
?引用來將結(jié)果集 ?ResultMap
?映射到參數(shù)的類型上。要注意這里的 ?javaType
?屬性是可選的,如果留空并且 ?jdbcType
?是 ?CURSOR
?,它會被自動(dòng)地被設(shè)為 ?ResultMap
?。
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
MyBatis 也支持很多高級的數(shù)據(jù)類型,比如結(jié)構(gòu)體(structs),但是當(dāng)使用 out 參數(shù)時(shí),你必須顯式設(shè)置類型的名稱。比如(再次提示,在實(shí)際中要像這樣不能換行):
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}
盡管上面這些選項(xiàng)很強(qiáng)大,但大多時(shí)候,你只須簡單指定屬性名,頂多要為可能為空的列指定 ?jdbcType
?,其他的事情交給 MyBatis 自己去推斷就行了。
#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
默認(rèn)情況下,使用 ?#{}
? 參數(shù)語法時(shí),MyBatis 會創(chuàng)建 ?PreparedStatement
?參數(shù)占位符,并通過占位符安全地設(shè)置參數(shù)(就像使用 ? 一樣)。 這樣做更安全,更迅速,通常也是首選做法,不過有時(shí)你就是想直接在 SQL 語句中直接插入一個(gè)不轉(zhuǎn)義的字符串。 比如 ORDER BY 子句,這時(shí)候你可以:
ORDER BY ${columnName}
這樣,MyBatis 就不會修改或轉(zhuǎn)義該字符串了。
當(dāng) SQL 語句中的元數(shù)據(jù)(如表名或列名)是動(dòng)態(tài)生成的時(shí)候,字符串替換將會非常有用。 舉個(gè)例子,如果你想 select 一個(gè)表任意一列的數(shù)據(jù)時(shí),不需要這樣寫:
@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);
@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);
@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);
// 其它的 "findByXxx" 方法
而是可以只寫這樣一個(gè)方法:
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
其中 ?${column}
? 會被直接替換,而 ?#{value}
? 會使用 ??
? 預(yù)處理。 這樣,就能完成同樣的任務(wù):
User userOfId1 = userMapper.findByColumn("id", 1L);
User userOfNameKid = userMapper.findByColumn("name", "kid");
User userOfEmail = userMapper.findByColumn("email", "noone@nowhere.com");
這種方式也同樣適用于替換表名的情況。
用這種方式接受用戶的輸入,并用作語句參數(shù)是不安全的,會導(dǎo)致潛在的 SQL 注入攻擊。因此,要么不允許用戶輸入這些字段,要么自行轉(zhuǎn)義并檢驗(yàn)這些參數(shù)。
更多建議: