1、參數獲取方式
MyBatis可以通過以下兩種方式獲取參數值:
-
#{變量名}
本質是占位符賦值 -
${變量名}
本質是字符串拼接,如果拼接的是字符串類型或日期類型,則需要手動添加單引號
2、參數獲取的幾種情況:
2.1 mapper接口方法的參數為單個字面量類型
可以通過${}
和#{}
以任意名稱獲取變量值,需注意${}
的單引號問題
如下:
public interface UserMapper {User selectUserByUserName(String username);}
<select id="selectUserByUserName" resultType="User"><!-- select * from t_user where username = '${username}' -->select * from t_user where username = #{username}</select>
2.2 mapper接口方法的參數為多個時
mybatis會將多個參數封裝到map集合中,并以兩種方式存儲:
- 方式一:以arg0,arg1,arg2,…為鍵,以參數為值
- 方式二:以param1,param2,param3,…為鍵,以參數為值
可以通過#{}
和${}
兩種方式以鍵的方式獲取值即可,需注意${}
的單引號問題
如下:
List<User> selectUserByUserNameAndAge(String username, Integer age, String sex);
<select id="selectUserByUserNameAndAge" resultType="User"><!-- select * from t_user where username = '${arg0}' and age = ${arg1} and sex = '${arg2}' -->select * from t_user where username = #{arg0} and age = #{arg1} and sex = #{arg2}</select>
2.3 mapper接口方法有多個參數時,手動封裝map參數
可以通過#{}
和${}
兩種方式以鍵的方式獲取值即可,需注意${}
的單引號問題
如下:
@Testpublic void testQueryByMapParam(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);Map<String, Object> map = new HashMap<>();map.put("username", "李斯");map.put("age", 23);map.put("sex", "女");List<User> admin = userMapper.selectUserByMap(map);admin.stream().forEach(System.out::println);}
List<User> selectUserByMap(Map<String, Object> map);
<select id="selectUserByMap" resultType="User">select * from t_user where username = #{username} and age = #{age} and sex = #{sex}</select>
2.4 mapper接口方法的參數為實體類類型的參數
可以通過${}
和#{}
以任意名稱獲取變量值,需注意${}
的單引號問題
如下:
@Testpublic void testInsertUser(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User user = new User();user.setUsername("中華");user.setPassword("123456");user.setAge(32);user.setSex("男");int i = userMapper.insertUser(user);System.out.println(i);}
int insertUser(User user);
<insert id="insertUser">insert into t_user values(null, #{username}, #{password}, #{age}, #{sex})</insert>
2.5 使用@Param注解命名參數
mybatis會將多個參數封裝到map集合中,并以兩種方式存儲:
- 方式一:以@Param注解的值為鍵,以參數為值
- 方式二:以param1,param2,param3,…為鍵,以參數為值
因此可以通過${}
和#{}
以任意名稱獲取變量值,需注意${}
的單引號問題
@Testpublic void testQueryByParam(){SqlSession sqlSession = SqlSessionUtil.getSqlSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);List<User> admin = userMapper.selectUserByParam("李斯", 23, "女");admin.stream().forEach(System.out::println);}
List<User> selectUserByParam(@Param("username") String username, @Param("age") Integer age, @Param("sex") String sex);
<select id="selectUserByParam" resultType="User">select * from t_user where username = #{username} and age = #{age} and sex = #{sex}</select>
3、MyBatis參數解析原理:
3.1 參數封裝
主要分析MyBatis對接口參數的處理,如參數的封裝、參數值的獲取原理。
org.apache.ibatis.reflection.ParamNameResolver#ParamNameResolverorg.apache.ibatis.reflection.ParamNameResolver#getNamedParams
- ParamNameResolver構造
構造方法中處理方法參數的映射,建立參數的索引和參數名的映射關系。
// 參數名稱處理public ParamNameResolver(Configuration config, Method method) {// 是否使用真實的參數名,默認為truethis.useActualParamName = config.isUseActualParamName();// 獲取方法的參數類型數組final Class<?>[] paramTypes = method.getParameterTypes();// 獲取方法參數注解final Annotation[][] paramAnnotations = method.getParameterAnnotations();// 存儲參數信息臨時對象final SortedMap<Integer, String> map = new TreeMap<>();int paramCount = paramAnnotations.length;// get names from @Param annotationsfor (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {/**The key is the index and the value is the name of the parameter. The name is obtained from Param if specified. When 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. RowBounds or ResultHandler).aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}*/if (isSpecialParameter(paramTypes[paramIndex])) {// 跳過特殊參數 ,如RowBounds 或 ResultHandlercontinue;}String name = null;for (Annotation annotation : paramAnnotations[paramIndex]) {if (annotation instanceof Param) {// 當前參數存在@Param注解時,參數名設置為注解的value值hasParamAnnotation = true;name = ((Param) annotation).value();break;}}if (name == null) {// 如果存在@Param注解,且沒有指定參數名if (useActualParamName) {// 取出方法參數名,如果時單一普通參數,則返回arg0name = 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);}
- getNamedParams
建立參數名和參數值的映射關系,并返回參數值
/*** <p>* A single non-special parameter is returned without a name. Multiple parameters are named using the naming rule. In* addition to the default names, this method also adds the generic names (param1, param2, ...).* </p>** @param args* the args** @return the named params*/public Object getNamedParams(Object[] args) {final int paramCount = names.size();if (args == null || paramCount == 0) {return null;}// 單一參數,且沒有@Param注解時if (!hasParamAnnotation && paramCount == 1) {Object value = args[names.firstKey()];// 此處會對集合類型的參數進行包裝處理return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : null);} else {// 建立參數名和值的映射,key:參數名 value:參數值final Map<String, Object> param = new ParamMap<>();int i = 0;for (Map.Entry<Integer, String> entry : names.entrySet()) {// 1.添加命名參數和參數值的映射關系param.put(entry.getValue(), args[entry.getKey()]);// add generic param names (param1, param2, ...)final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);// ensure not to overwrite parameter named with @Paramif (!names.containsValue(genericParamName)) {// 2.同時添加一個以param1,param2...為參數名 和 參數值的映射關系param.put(genericParamName, args[entry.getKey()]);}i++;}return param;}}
wrapToMapIfCollection
/*** Wrap to a {@link ParamMap} if object is {@link Collection} or array.** @param object* a parameter object* @param actualParamName* an actual parameter name (If specify a name, set an object to {@link ParamMap} with specified name)** @return a {@link ParamMap}** @since 3.5.5*/public static Object wrapToMapIfCollection(Object object, String actualParamName) {// 如果是集合類型,則包裝key為collection、list的map返回if (object instanceof Collection) {ParamMap<Object> map = new ParamMap<>();map.put("collection", object);if (object instanceof List) {map.put("list", object);}Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));return map;}// 如果是數據組類型,則包裝key為array的map返回if (object != null && object.getClass().isArray()) {ParamMap<Object> map = new ParamMap<>();map.put("array", object);Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));return map;}// 如果不是集合類型或數組類型,則直接返回當前值return object;}
3.2 參數調用
在org.apache.ibatis.binding.MapperMethod#execute方法中,通過convertArgsToSqlCommandParam獲取參數值。
public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {// 獲取參數值Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {throw new BindingException("Mapper method '" + command.getName()+ "' attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");}return result;}