引言
今天我們來(lái)說(shuō) MyBatis 接收參數(shù)這一塊斯棒。我打算這樣說(shuō)給你聽(tīng)荣暮,我們先看一下MyBatis源碼是如何處理參數(shù)的罩驻,然后我們通過(guò)例子來(lái)教你惠遏。
實(shí)際上抽高,我們這一節(jié)講的就是:Mapper.xml
如何獲取 Dao
中的參數(shù)呢透绩?
另外帚豪,如果你還沒(méi)有開(kāi)始學(xué)習(xí)MyBatis狸臣,覺(jué)得MyBatis還不錯(cuò),可以看 【MyBatis】學(xué)習(xí)紀(jì)要一:SpringBoot集成MyBatis完成增刪查改 這篇教程统翩,起步厂汗。
源碼分析
- 第一步:我們先找到源碼娶桦。
我先將處理參數(shù)的類(lèi)復(fù)制到下面,然后我們?cè)僖黄饋?lái)分析栗涂。
/**
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.reflection;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
public class ParamNameResolver {
private static final String GENERIC_NAME_PREFIX = "param";
/**
* <p>
* The key is the index and the value is the name of the parameter.<br />
* The name is obtained from {@link Param} if specified. When {@link Param} is not specified,
* the parameter index is used. Note that this index could be different from the actual index
* when the method has special parameters (i.e. {@link RowBounds} or {@link ResultHandler}).
* </p>
* <ul>
* <li>aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}</li>
* <li>aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}</li>
* <li>aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}</li>
* </ul>
*/
private final SortedMap<Integer, String> names;
private boolean hasParamAnnotation;
public ParamNameResolver(Configuration config, Method method) {
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
private String getActualParamName(Method method, int paramIndex) {
if (Jdk.parameterExists) {
return ParamNameUtil.getParamNames(method).get(paramIndex);
}
return null;
}
private static boolean isSpecialParameter(Class<?> clazz) {
return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
}
/**
* Returns parameter names referenced by SQL providers.
*/
public String[] getNames() {
return names.values().toArray(new String[0]);
}
/**
* <p>
* A single non-special parameter is returned without a name.<br />
* Multiple parameters are named using the naming rule.<br />
* In addition to the default names, this method also adds the generic names (param1, param2,
* ...).
* </p>
*/
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
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;
}
}
}
- 第二步:我們先來(lái)看構(gòu)成方法忿墅。
public ParamNameResolver(Configuration config, Method method) {
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
這部分代碼還是挺好理解的疚脐,可以先大概看一篇棍弄,然后主要看注釋的幾處呼畸。
1役耕、獲取有 @Param
注解的值
2聪廉、每次解析一個(gè)參數(shù)板熊,并且保存到map中(key:索引察绷,value:name)
name:標(biāo)注了Param注解:注解的值
沒(méi)有標(biāo)注:
1.全局配置:useActualParamName(jdk1.8):name=參數(shù)名
2.name=map.size():相當(dāng)于當(dāng)前元素的索引
例如:
method(@Param("id") id, @Param("name") name, Integer age);
之后的Map干签,大概就這樣:{0=id, 1=name}
- 第三步:我們具體分析
// args[1, "Tom", 20]
public Object getNamedParams(Object[] args) {
int paramCount = this.names.size();
// 參數(shù)為null,直接返回
if (args != null && paramCount != 0) {
// 如果只有一個(gè)元素拆撼,并且沒(méi)有 `@Param` 注解容劳,args[0]喘沿,單個(gè)參數(shù)直接返回
if (!this.hasParamAnnotation && paramCount == 1) {
return args[(Integer)this.names.firstKey()];
// 多個(gè)參數(shù)或者有 `@Param` 標(biāo)注
} else {
Map<String, Object> param = new ParamMap();
int i = 0;
// 遍歷 names 集合{0=id, 1=name}
for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
// names集合的value作為key;
// names集合的key又作為取值的參考args[0]:args[1, "Tom"]
// eg:{id=args[0]:1, name=args[1]:Tome}
Entry<Integer, String> entry = (Entry)var5.next();
param.put(entry.getValue(), args[(Integer)entry.getKey()]);
// 額外的將每一個(gè)參數(shù)也保存到map中竭贩,使用新的key:param1 ... paramN
String genericParamName = "param" + String.valueOf(i + 1);
if (!this.names.containsValue(genericParamName)) {
param.put(genericParamName, args[(Integer)entry.getKey()]);
}
}
return param;
}
} else {
return null;
}
}
通過(guò)讀源碼中的注釋?zhuān)嘈拍愦蟾哦税裳劣 H绻欢矝](méi)關(guān)系,我們通過(guò)實(shí)例演示給你窄赋。
演示
這一步,我們先搭建起項(xiàng)目错敢,先看搭建之后的目錄結(jié)構(gòu)。如果你已經(jīng)搭建起來(lái)峰锁,可以調(diào)到下一步虹蒋。
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_demo
spring.datasource.username=root
spring.datasource.password=xfsy2018
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
mybatis.type-aliases-package=com.fengwenyi.mybatis.demo1.domain
mybatis.mapper-locations=classpath:mapper/*.xml
User
package com.fengwenyi.mybatis.demo1.domain;
/**
* @author Wenyi Feng
*/
public class User {
private Integer id;
private String name;
private Integer age;
// getter & setter
// toString
}
UserDao
package com.fengwenyi.mybatis.demo1.dao;
import com.fengwenyi.mybatis.demo1.domain.User;
import org.apache.ibatis.annotations.Mapper;
/**
* @author Wenyi Feng
*/
@Mapper
public interface UserDao {
// 查詢(xún)
User findById (Integer id);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.fengwenyi.mybatis.demo1.dao.UserDao" >
<resultMap id="BaseResultMap" type="User" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="age" property="age" jdbcType="INTEGER" />
</resultMap>
<select id="findById" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
user
WHERE
id = #{id}
</select>
</mapper>
數(shù)據(jù)庫(kù)
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4;
UserService
package com.fengwenyi.mybatis.demo1.service;
import com.fengwenyi.mybatis.demo1.dao.UserDao;
import com.fengwenyi.mybatis.demo1.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author Wenyi Feng
*/
@Service
public class UserService {
@Autowired
private UserDao userDao;
public User findById (Integer id) {
if (id != null && id > 0) {
return userDao.findById(id);
}
return null;
}
}
看一下測(cè)試代碼
Demo1ApplicationTests
package com.fengwenyi.mybatis.demo1;
import com.fengwenyi.mybatis.demo1.domain.User;
import com.fengwenyi.mybatis.demo1.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class Demo1ApplicationTests {
@Test
public void contextLoads() {
}
@Autowired
private UserService userService;
@Test
public void test1 () {
User user = userService.findById(1);
System.out.println(user.toString());
}
}
運(yùn)行:
Demo1ApplicationTests > test1()
只有一個(gè)參數(shù)
上面例子就是一個(gè)參數(shù)標(biāo)準(zhǔn)實(shí)例哲银,我們抽樣一下荆责。
UserDao
User findById (Integer id);
UserMapper.xml
<select id="findById" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
user
WHERE
id = #{id}
</select>
一個(gè)參數(shù),不論你用什么都可以取到键耕,例如 id=#{ssss}
玛迄。
多個(gè)參數(shù)
UserDao
User findByNameAndAge (String name, Integer age);
這樣寫(xiě)怎么獲取參數(shù)呢?
你可能會(huì)這樣勒虾,我們寫(xiě)一個(gè)例子測(cè)試下
UserMapp.xml
<select id="findByNameAndAge" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
user
WHERE
name = #{name}
AND
age = #{age}
</select>
運(yùn)行會(huì)報(bào)錯(cuò):
Caused by:
org.apache.ibatis.binding.BindingException:
Parameter 'name' not found.
Available parameters are [arg1, arg0, param1, param2]
這樣寫(xiě),不對(duì)愕宋,那我們?cè)撛趺磳?xiě)呢?
<select id="findByNameAndAge" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
user
WHERE
name = #{param1}
AND
age = #{param2}
</select>
原因邻寿,開(kāi)始我們就說(shuō)了。
@Param
我們改善一下
UserDao
User findByNameAndAge2 (@Param("name") String name, @Param("age") Integer age);
UserMapper.xml
<select id="findByNameAndAge2" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
user
WHERE
name = #{name}
AND
age = #{age}
</select>
POJO
UserDao
User findByPojo (User user);
UserMapper.xml
<select id="findByPojo" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
user
WHERE
name = #{name}
AND
age = #{age}
</select>
Map
看源碼我們已經(jīng)知道,MyBatis會(huì)將我們傳遞的參數(shù)封裝成Map段磨,那我們直接傳Map會(huì)怎么樣呢薇溃?
UserDao
User findByMap (Map<String, Object> map);
UserMapper.xml
<select id="findByMap" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
user
WHERE
name = #{name}
AND
age = #{age}
</select>
List
下面這兩種是特殊的,但我還是說(shuō)給你聽(tīng)
User findByList (List<Object> list);
<select id="findByList" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
user
WHERE
name = #{list[0]}
AND
age = #{list[1]}
</select>
Array
數(shù)組怎么處理
User findByArray (Object [] array);
<select id="findByArray" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
user
WHERE
name = #{array[0]}
AND
age = #{array[1]}
</select>
$ AND #
取值我們都會(huì)用 #{}
。
在我們習(xí)慣用的 ${}
特姐,你們覺(jué)得這個(gè)很特別嗎?有沒(méi)有想過(guò)為什么呢捷枯?
- #
UserDao
User testGetValue1 (String name);
UserMapper.xml
<select id="testGetValue1" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
user
WHERE
name = #{name}
</select>
看下運(yùn)行日志:
==> Preparing: SELECT id, name, age FROM user WHERE name = ?
==> Parameters: AAA(String)
<== Columns: id, name, age
<== Row: 1, AAA, 20
<== Total: 1
我們繼續(xù)看
- $
UserDao
User testGetValue2 (@Param("name") String name);
UserMapper.xml
<select id="testGetValue2" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
user
WHERE
name = '${name}'
</select>
看下sql日志:
==> Preparing: SELECT id, name, age FROM user WHERE name = 'AAA'
==> Parameters:
<== Columns: id, name, age
<== Row: 1, AAA, 20
<== Total: 1
對(duì)比看下攀痊,你也許會(huì)發(fā)現(xiàn)各自的用法苟径。
值得注意的是,這里只是 $
的測(cè)試示例蹬碧,在實(shí)際中,不推薦這么寫(xiě)罗心。
$的用法
既然有 ${}
渤闷,那應(yīng)該怎么用呢?我們?cè)倏匆粋€(gè)例子
UserDao
User testGetValue3 (@Param("name") String name);
UserMapper.xml
<select id="testGetValue3" resultMap="BaseResultMap">
SELECT
id, name, age
FROM
${name}
WHERE
name = "AAA"
</select>
看下sql日志:
==> Preparing: SELECT id, name, age FROM user WHERE name = "AAA"
==> Parameters:
<== Columns: id, name, age
<== Row: 1, AAA, 20
<== Total: 1
總結(jié)
到這里就算差不過(guò)結(jié)束了肩碟。總結(jié)一下吧:
參數(shù)多時(shí)髓抑,封裝成Map,為了不混亂密末,我們可以使用
@Param
來(lái)指定封裝時(shí)使用的key严里,使用#{key}
就可以取出Map中的值