Mybatis介紹之參數(shù)傳遞
1 使用Mapper接口時參數(shù)傳遞方式
Mybatis在使用Mapper接口進行編程時,其實底層是采用了動態(tài)代理機制,表面上是調(diào)用的Mapper接口,而實際上是通過動態(tài)代理調(diào)用的SqlSession的對應(yīng)方法,如selectOne(),有興趣的朋友可以查看DefaultSqlSession的getMapper()方法實現(xiàn),其最終會獲得一個代理了Mapper接口的MapperProxy對象。MapperProxy對象在調(diào)用Mapper接口方法時會把傳遞的參數(shù)做一個轉(zhuǎn)換,然后把轉(zhuǎn)換后的參數(shù)作為入?yún)⒄{(diào)用SqlSession對應(yīng)的操作方法(如selectOne、insert等)。轉(zhuǎn)換過程可以參考MapperMethod的execute()方法實現(xiàn)。簡單來說是以下規(guī)則:
1、如果傳遞過來是單參數(shù),且沒有以@Param注解進行命名,則直接將單參數(shù)作為真實的參數(shù)調(diào)用SqlSession的對應(yīng)方法。
2、如果傳遞過來的不是單參數(shù)或者是包含以@Param注解進行命名的參數(shù),則會將對應(yīng)的參數(shù)轉(zhuǎn)換為一個Map進行傳遞。具體規(guī)則如下:
2.1、 會把對應(yīng)的參數(shù)按照順序以param1、param2、paramN這樣的形式作為Key存入目標(biāo)Map中,第一個參數(shù)是param1,第N個參數(shù)是paramN。
2.2、 如果參數(shù)是以@Param注解命名的參數(shù),則以@Param指定的名稱作為Key存入目標(biāo)Map中。
2.3、 如果參數(shù)不是以@Param注解命名的,則按照順序以0、1、N這樣的形式作為Key存入目標(biāo)Map中,第一個參數(shù)是0,第N個參數(shù)是N。
我們在Mapper.xml中定義SQL語句中用到的變量最終是以Mybatis按照上述規(guī)則把入?yún)⑥D(zhuǎn)換后的形式來進行解析的。
2 參數(shù)引用#與$的區(qū)別
在Mybatis的Mapper.xml文件中為了構(gòu)建動態(tài)的SQL,我們通常需要通過引用傳遞的參數(shù)來達到對應(yīng)的效果。在引用傳遞的參數(shù)的時候有兩種方式,一種是通過“#{var}”的形式引用,一種是通過“${var}”的形式引用。前者會被以預(yù)編譯的方式傳遞,即會生成PreparedStatement來處理,對應(yīng)的參數(shù)在SQL中會以“?”占位,然后調(diào)用PreparedStatement的相應(yīng)API設(shè)值;而后者則會先處理好變量再執(zhí)行相應(yīng)的SQL,即對應(yīng)的變量會先被替換,再執(zhí)行替換了變量的SQL。在一個Mapper映射語句中我們可以同時使用“#”和“$”處理變量。
3 采用#{var}的形式處理變量
采用#{var}的形式來引用變量時,其中的變量會在解析Mapper.xml文件中的語句時就被替換為占位符“?”,同時通過ParameterMapping類記錄對應(yīng)的變量信息。在真正執(zhí)行對應(yīng)的語句時會用傳遞的真實參數(shù)根據(jù)ParameterMapping信息給PreparedStatement設(shè)置參數(shù),具體可參考PreparedStatementHandler的parameterize()方法實現(xiàn)。對應(yīng)的變量在解析的時候都是以傳遞進來的參數(shù)為基礎(chǔ)來解析的,根據(jù)前面的介紹我們知道,傳遞進來的參數(shù)只有兩種情況,一種是原始類型的單參數(shù),一種是被Mybatis封裝過的Map。那對應(yīng)的參數(shù)Mybatis的如何解析的呢?有三種情況。
第一種,如果真實傳遞進來的是原始類型,那么對應(yīng)的屬性解析就是就是基于原始類型的屬性。比如我們真是傳遞進來的一個User類型的對象,User對象有id、name等屬性,那么我們在Mapper語句中采用#{var}形式訪問變量的時候就可以使用#{id}和#{name}。這跟使用${var}形式訪問變量是不同的,使用${var}形式時我們需要使用${_parameter.id}和${_parameter.name}。
<insert id="insert"parameterType="com.elim.learn.mybatis.model.User"useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into t_user(name,username,email,mobile) values(#{name},#{username},#{email},#{mobile})
</insert>
第二種,如果真實傳遞進來的是一個Map,那么對應(yīng)的變量則可以是這個Map里面的Key。比如下面示例中我們的接口方法對應(yīng)的是兩個參數(shù),那最終到執(zhí)行Mapper語句時會被封裝成一個Map,Map里面的Key包含name、1、param1和param2,那么我們在使用的時候就可以使用這些變量。
List<User> findByNameAndMobile(@Param("name") String name, Stringmobile);
<select id="findByNameAndMobile"resultType="com.elim.learn.mybatis.model.User">
select id,name,username,email,mobile from t_user where name=#{name} and mobile=#{1}
</select>
第三種,真實傳遞進來的參數(shù)是可以被已經(jīng)注冊了的TypeHandler進行處理的,則直接使用真實傳遞進來的參數(shù)作為真實的變量值。這也是為什么我們經(jīng)常可以在Mapper接口里面寫delete(Integer id),然后在Mapper語句中直接寫#{id}的原因,因為這個時候傳遞進來的參數(shù)是Integer類型,是可以直接被處理的,Mybatis將直接拿它的值作為當(dāng)前變量id的值,而不會去取傳遞進來的值的id屬性。換句話說,這個時候我們在Mapper語句中定義的變量可以隨便命名,隨便怎么命名都可以被Mybatis正確的處理。但是通常我們會把它命名為與接口方法參數(shù)名稱一致,方便閱讀。
void delete(Long id);
<insert id="delete" parameterType="java.lang.Long">
delete t_user where id=#{id}
</insert>
關(guān)于預(yù)編譯的參數(shù)處理是由DefaultParameterHandler類的setParameters()方法處理,核心代碼如下,欲了解更多,可以參考DefaultParameterHandler的完整代碼。
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings =boundSql.getParameterMappings();
if (parameterMappings != null) {
for (inti = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject =configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
4 采用${var}的形式處理變量
采用“${var}”的形式來引用變量時,其中的變量會在MappedStatement調(diào)用getBoundSql()方法獲取對應(yīng)的BoundSql時被替換。${var}中的var可以是如下取值:
1、內(nèi)置的_parameter變量,對應(yīng)轉(zhuǎn)換后的傳遞參數(shù),在只傳遞單參數(shù)且是沒有使用@Param注解對參數(shù)命名的時候如果我們需要通過${var}的形式來訪問傳遞的單參數(shù),則可以使用${_parameter};
2、如果對應(yīng)的Mapper接口方法是多參數(shù)或者擁有@Param命名的參數(shù)時可以使用param1、paramN的形式;
3、如果對應(yīng)的Mapper接口方法參數(shù)是@Param命名的方法參數(shù),則可以使用@Param指定的名稱;
4、如果對應(yīng)的Mapper接口方法擁有多個參數(shù),且擁有沒有使用@Param命名的參數(shù),那沒有使用@Param命名的參數(shù)可以通過0、1、N形式訪問。
根據(jù)上述規(guī)則如我們有一個findById的方法其接收一個Long類型的參數(shù)作為ID,當(dāng)使用${var}的形式引用變量時則可以寫成如下這樣:
<select id="findById"resultType="com.elim.learn.mybatis.model.User"parameterType="java.lang.Long" >
select id,name,username,email,mobile from t_user where id=${_parameter}
</select>
當(dāng)我們的Mapper接口方法參數(shù)使用了@Param命名的時候,我們還可以使用@Param指定的參數(shù)名。
public interface UserMapper {
User findById(@Param("id") Long id);
}
<select id="findById"resultType="com.elim.learn.mybatis.model.User"parameterType="java.lang.Long" >
select id,name,username,email,mobile from t_user where id=${id}
</select>
注意,但是使用了@Param對單參數(shù)命名后我們就不能再在Mapper語句中通過${_parameter}來引用接口方法參數(shù)傳遞過來的單參數(shù)了,因為此時其已經(jīng)被包裝為一個Map了,如果要通過_parameter來引用的話,我們應(yīng)該使用${_parameter.param1}或${_parameter.varName},對于上面的示例來說就是${_parameter.param1}或${_parameter.id}。
下面我們來看一個傳遞多參數(shù)的,假設(shè)我們有如下這樣的一個Mapper語句及對應(yīng)的Mapper接口方法,這個Mapper接口方法接收兩個參數(shù),第一個參數(shù)是用@Param注解指定了參數(shù)名為name,第二個參數(shù)是沒有使用注解的,具體如下。
<!-- 當(dāng)對應(yīng)的接口方法傳遞多個參數(shù)時,可以不指定parameterType參數(shù),就算指定了也沒用,因為這個時候默認是Map -->
<select id="findByNameAndMobile"resultType="com.elim.learn.mybatis.model.User">
select id,name,username,email,mobile from t_user where name='${name}' and mobile='${1}'
</select>
List<User> findByNameAndMobile(@Param("name") String name, Stringmobile);
那我們的Mapper.xml文件中的對應(yīng)語句需要Mapper接口方法參數(shù)時有哪幾種方式呢?按照之前的規(guī)則,對于第一個方法參數(shù)name而言,因為使用了@Param指定了參數(shù)名name,所以我們可以在Mapper.xml文件中通過變量name和param1來引用它,而第二個參數(shù)mobile是沒有使用@Param指定參數(shù)名的,則我們可以使用param2和參數(shù)相對位置“1”來引用它。如上面的示例中,我們就是通過第二個參數(shù)的相對位置“1”來引用的。如果我們把第二個參數(shù)改為${mobile}是引用不到,且系統(tǒng)會報如下錯誤,有興趣的朋友可以試一下。
org.apache.ibatis.binding.BindingException: Parameter 'mobile' not found. Available parameters are [1, name, param1, param2]
一般情況下為了防止SQL注入問題,是不建議直接在where條件中使用${var}的形式訪問變量的,通常會用預(yù)編譯形式的#{var}。而${var}往往是用來傳遞一些非where條件的內(nèi)容,比如排序信息,具體用法請根據(jù)實際情況決定。

浙公網(wǎng)安備 33010602011771號