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