之前見到的所有語句都使用了簡單的參數(shù)形式。但實際上,參數(shù)是 MyBatis 非常強大的元素。對于大多數(shù)簡單的使用場景,你都不需要使用復(fù)雜的參數(shù),比如:
<select id="selectUsers" resultType="User">
select id, username, password
from users
where id = #{id}
</select>
上面的這個示例說明了一個非常簡單的命名參數(shù)映射。鑒于參數(shù)類型(?parameterType
?)會被自動設(shè)置為 int,這個參數(shù)可以隨意命名。原始類型或簡單數(shù)據(jù)類型(比如 Integer 和 String)因為沒有其它屬性,會用它們的值來作為參數(shù)。 然而,如果傳入一個復(fù)雜的對象,行為就會有點不一樣了。比如:
<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ù)映射的功能遠不止于此。
首先,和 MyBatis 的其它部分一樣,參數(shù)也可以指定一個特殊的數(shù)據(jù)類型。
#{property,javaType=int,jdbcType=NUMERIC}
和 MyBatis 的其它部分一樣,幾乎總是可以根據(jù)參數(shù)對象的類型確定 ?javaType
?,除非該對象是一個 ?HashMap
?。這個時候,你需要顯式指定 ?javaType
?來確保正確的類型處理器(?TypeHandler
?)被使用。
?JDBC
?要求,如果一個列允許使用 ?null
?值,并且會使用值為 ?null
?的參數(shù),就必須要指定 ?JDBC
?類型(?jdbcType
?)。
要更進一步地自定義類型處理方式,可以指定一個特殊的類型處理器類(或別名),比如:
#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
參數(shù)的配置好像越來越繁瑣了,但實際上,很少需要如此繁瑣的配置。
對于數(shù)值類型,還可以設(shè)置 ?numericScale
?指定小數(shù)點后保留的位數(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
?),你必須指定一個 ?resultMap
?引用來將結(jié)果集 ?ResultMap
?映射到參數(shù)的類型上。要注意這里的 ?javaType
?屬性是可選的,如果留空并且 ?jdbcType
?是 ?CURSOR
?,它會被自動地被設(shè)為 ?ResultMap
?。
#{department, mode=OUT, jdbcType=CURSOR, javaType=ResultSet, resultMap=departmentResultMap}
MyBatis 也支持很多高級的數(shù)據(jù)類型,比如結(jié)構(gòu)體(structs),但是當(dāng)使用 out 參數(shù)時,你必須顯式設(shè)置類型的名稱。比如(再次提示,在實際中要像這樣不能換行):
#{middleInitial, mode=OUT, jdbcType=STRUCT, jdbcTypeName=MY_TYPE, resultMap=departmentResultMap}
盡管上面這些選項很強大,但大多時候,你只須簡單指定屬性名,頂多要為可能為空的列指定 ?jdbcType
?,其他的事情交給 MyBatis 自己去推斷就行了。
#{firstName}
#{middleInitial,jdbcType=VARCHAR}
#{lastName}
默認情況下,使用 ?#{}
? 參數(shù)語法時,MyBatis 會創(chuàng)建 ?PreparedStatement
?參數(shù)占位符,并通過占位符安全地設(shè)置參數(shù)(就像使用 ? 一樣)。 這樣做更安全,更迅速,通常也是首選做法,不過有時你就是想直接在 SQL 語句中直接插入一個不轉(zhuǎn)義的字符串。 比如 ORDER BY 子句,這時候你可以:
ORDER BY ${columnName}
這樣,MyBatis 就不會修改或轉(zhuǎn)義該字符串了。
當(dāng) SQL 語句中的元數(shù)據(jù)(如表名或列名)是動態(tài)生成的時候,字符串替換將會非常有用。 舉個例子,如果你想 select 一個表任意一列的數(shù)據(jù)時,不需要這樣寫:
@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" 方法
而是可以只寫這樣一個方法:
@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)義并檢驗這些參數(shù)。
更多建議: