MyBatis 入门教程之三

SQL 映射文件

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。SQL 映射文件只有很少的几个顶级标签(按照应被定义的顺序列出):

  • cache – 该命名空间的缓存配置
  • cache-ref – 引用其它命名空间的缓存配置
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的标签
  • parameterMap – 老式风格的参数映射,此标签已被废弃,并可能在将来被移除,请使用行内参数映射
  • sql – 可被其它语句引用的可重用语句块
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

增删改查

本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-4

select 标签

查询标签是 MyBatis 中最常用的标签之一,光能把数据存到数据库中价值并不大,还要能重新取出来才有用,多数应用也都是查询比修改要频繁。MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作。因此,MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 标签是非常简单的。比如:

1
2
3
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
</select>

这个语句名为 selectPerson,接受一个 int(Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。特别注意参数符号 #{id},这是用来告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个 ? 来标识,并被传递到一个新的预处理语句中,就像这样:

1
2
3
4
long id = 1001;
String selectPerson = "SELECT * FROM PERSON WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectPerson);
ps.setInt(1, id);

select 标签允许配置很多属性来配置每条语句的行为细节,例如:

1
2
3
4
5
6
7
8
9
10
11
12
<select
id="selectPerson"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="personResultMap"
flushCache="false"
useCache="true"
timeout="10"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD_ONLY">

insert、update、delete 标签

数据更改语句 insertupdatedelete 的使用非常接近:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<insert
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">

<update
id="updateAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">

<delete
id="deleteAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">

下面是 insertupdatedelete 语句的使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<insert id="insertAuthor" parameterType="domain.blog.Author">
insert into Author (id, username, password, email,bio)
values (#{id}, #{username}, #{password}, #{email}, #{bio})
</insert>

<update id="updateAuthor" parameterType="domain.blog.Author">
update Author set
username = #{username},
password = #{password},
email = #{email},
bio = #{bio}
where id = #{id}
</update>

<delete id="deleteAuthor">
delete from Author where id = #{id}
</delete>

提示

MyBatis 允许增删改的 DAO 接口直接定义以下类型的返回值,包括:int、long、boolean、Integer、Long、Boolean,这可用于判断增删改操作是否执行成功。

Bind 绑定

在项目开发中,经常会使用到 like 查询条件来实现模糊查询,例如:

1
2
3
4
5
public interface EmployeeMapper {

public List<Employee> getByLastName(String lastName);

}
1
2
3
4
5
6
7
8
9
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<select id="getByLastName" resultType="com.clay.mybatis.bean.Employee">
select id, last_name as lastName, gender, email
from t_employee
where last_name like concat('%', #{lastName}, '%')
</select>

</mapper>

由于 MySQL 的 concat() 字符串连接函数支持多个参数,而 Oracle 的 concat() 函数只支持两个参数,因此上述的 SQL 在 Oracle 数据库会执行失败。由于不同数据库之间的语法差异,如果更换数据库,有些 SQL 语句可能就需要重写。针对这种情况,可以使用 bind 标签来避免因更换数据库而修改 SQL,同时也能防止 SQL 注入。

提示

  • bind 标签可以使用 OGNL 表达式创建一个变量,并将其绑定到上下文中
  • bind 标签的 name 属性是绑定到上下文的变量名,value 是 OGNL 表达式

将上述的 SQL 改为 bind 方式后,代码如下。

1
2
3
4
5
6
7
8
9
10
11
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<!-- 将 OGNL 表达式的值绑定到一个变量中,这样方便后面引用这个变量的值 -->
<select id="getByLastName" resultType="com.clay.mybatis.bean.Employee">
<bind name="_lastName" value="'%' + lastName + '%'" />
select id, last_name as lastName, gender, email
from t_employee
where last_name like #{_lastName}
</select>

</mapper>

SQL 重用

sql 标签可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 标签中定义不同的参数值。本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-5

使用案例一

1
<sql id="userColumns"> id, username, password </sql>

上面的 SQL 片段可以在其它语句中使用,例如:

1
2
3
4
5
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"></include>
from some_table
</select>

使用案例二

或者配合 if 标签一起使用,_databaseId 是 MyBatis 的内置参数:

1
2
3
4
5
6
7
8
<sql id="userColumns">
<if test="_databaseId == 'oracle'">
id, username, email
</if>
<if test="_databaseId == 'mysql'">
id, username, gender
</if>
</sql>

使用案例三

或者稍微复杂一点的使用(带参数替换),例如:

1
<sql id="userColumns"> ${alias}.id, ${alias}.username, ${alias}.password </sql>
1
2
3
4
5
6
7
8
9
10
11
12
<select id="selectUsers" resultType="map">
select
<include refid="userColumns">
<property name="alias" value="t1" />
</include>
,
<include refid="userColumns">
<property name="alias" value="t2" />
</include>
from some_table t1
cross join some_table t2
</select>

上面的写法,最终发出的 SQL 语句如下:

1
2
3
4
5
select
t1.id, t1.username, t1.password,
t2.id, t2.username, t2.password
from some_table t1
cross join some_table t2

使用案例四

或者在 include 标签的 refid 属性或内部语句中使用属性值,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<sql id="sometable">
${prefix}Table
</sql>

<sql id="someinclude">
from
<include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
select id, username, email
<include refid="someinclude">
<property name="prefix" value="User"/>
<property name="include_target" value="sometable"/>
</include>
</select>

上面的写法,最终发出的 SQL 语句如下:

1
select id, username, email from UserTable;

主键生成

如前所述,插入语句的配置规则更加丰富,在插入语句里面有一些额外的属性和子标签用来处理主键的生成,并且提供了多种生成方式。本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-5

自增主键生成

首先,如果数据库支持自增主键的字段(比如 MySQL 和 SQL Server),那么可以设置 useGeneratedKeys="true",然后再把 keyProperty 设置为目标属性就可以了。例如,如果上面的 Author 表已经在 id 列上使用了自增长,那么插入语句可以修改为:

1
2
3
4
<insert id="insertAuthor" parameterType="domain.blog.Author" useGeneratedKeys="true" keyProperty="id">
insert into Author (username, password, email, bio)
values (#{username}, #{password}, #{email}, #{bio})
</insert>

SQL 映射文件里配置了自增主键后,在 Java 代码里就可以直接通过 JavaBean 对象获取到数据库里自增后的主键值。

1
2
3
Author author = new Author("Tom", "123456", "tom@gmail.com", "");
mapper.insertAuthor(author);
System.out.println("id = " + author.getId());

如果数据库还支持多行插入,则也可以传入一个 Author 数组或集合,并返回自动生成的主键。

1
2
3
4
5
6
<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
insert into Author (username, password, email, bio) values
<foreach item="item" collection="list" separator=",">
(#{item.username}, #{item.password}, #{item.email}, #{item.bio})
</foreach>
</insert>

非自增主键生成

生成随机主键

对于不支持自动生成主键的数据库(Oracle)和可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键。这里有一个简单(也很傻)的示例,它可以生成一个随机 ID(不建议在生产环境中使用,这里只是为了展示 MyBatis 处理问题的灵活性和宽容度):

1
2
3
4
5
6
7
8
9
10
<insert id="insertAuthor">
<!-- 会自动将查出的主键值封装给JavaBean的指定属性(如 id) -->
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
insert into Author
(id, username, password, email,bio, favourite_section)
values
(#{id}, #{username}, #{password}, #{email}, #{bio}, #{favouriteSection, jdbcType=VARCHAR})
</insert>

在上面的示例中,首先会执行 selectKey 标签中的语句,并设置 Authorid 属性,然后才会调用插入语句。这样就实现了数据库自动生成主键类似的行为,同时保持了 Java 代码的简洁。selectKey 标签描述如下:

1
2
3
4
5
<selectKey
keyProperty="id"
resultType="int"
order="BEFORE"
statementType="PREPARED">

生成 Oracle 主键

Oracle 数据库不支持自增主键,但可以使用序列来模拟自增,即每次插入的数据的主键都是从序列中拿到的值。

  • 第一种方式(Before)
1
2
3
4
5
6
7
8
<insert id="addEmp" databaseId="oracle" parameterType="com.clay.mybatis.bean.Employee">
<!-- 查询主键的SQL语句,会自动将查出的主键值封装给JavaBean的指定属性(如 id) -->
<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
select EMPLOYEES_SEQ.nextval from dual
</selectKey>
insert into employees (id, last_name, email)
values(#{id}, #{lastName}, #{email})
</insert>

先执行 selectKey 标签中查询主键的 SQL 语句(从序列中拿到),然后将查出的主键值封装给 JavaBean 的指定属性(如 id),最后再执行插入数据的 SQL 语句。

  • 第二种方式(After)
1
2
3
4
5
6
7
8
<insert id="addEmp" databaseId="oracle" parameterType="com.clay.mybatis.bean.Employee">
<!-- 查询主键的SQL语句,会自动将查出的主键值封装给JavaBean的指定属性(如 id) -->
<selectKey keyProperty="id" order="AFTER" resultType="Integer">
select EMPLOYEES_SEQ.currval from dual
</selectKey>
insert into employees (id, last_name, email)
values(employees_seq.nextval, #{lastName}, #{email})
</insert>

先执行插入数据的 SQL 语句,再执行 selectKey 标签中查询主键的 SQL 语句(从序列中拿到),最后将查出的主键值封装给 JavaBean 的指定属性(如 id)。

特别注意

如果是批量插入多条数据,采用第二种方式(After)的写法,会导致在 Java 代码里只能拿到最后一条插入记录的主键值,因此推荐使用第一种方式(Before)。

参数处理

本节所需的案例代码,可以直接从 GitHub 下载对应章节 mybatis-lesson-6

单个参数

  • MyBatis 可以接受基本类型、对象类型、集合类型、数组类型的参数值,MyBatis 可直接使用这些类型的参数,不需要经过任何处理
  • 针对基本类型,可以使用 #{参数名 / 任意名} 取出传入的参数值
  • 针对对象类型、集合类型、数组类型的处理,详细说明请看后文的介绍
1
2
3
4
5
public interface EmployeeMapper {

public Employee getEmpById(Long id);

}
1
2
3
4
5
6
7
8
9
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<select id="getEmpById" parameterType="Long" resultType="com.clay.mybatis.bean.Employee">
select id, last_name as lastName, gender, email
from t_employee
where id = #{id}
</select>

</mapper>

多个参数

默认的处理方式
  • MyBatis 会做特殊处理
  • 多个参数会被封装成一个 Map,其中 key 默认是 param1 ... paramN,或者是使用带索引的 arg0 ... argN,而 value 则是 Java 方法依次传入的参数值
  • 举个例子,#{param1} 可以从 Map 中获取第一个 key 的值(Java 方法传入的第一个参数)
1
2
3
4
5
public interface EmployeeMapper {

public Employee getEmpByIdAndEmail(Long id, String email);

}
1
2
3
4
5
6
7
8
9
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<select id="getEmpByIdAndEmail" resultType="com.clay.mybatis.bean.Employee">
select id, last_name as lastName, gender, email
from t_employee
where id = #{param1} and email = #{param2}
</select>

</mapper>
使用注解的处理方式

通过 @Param 注解,明确指定 MyBatis 封装参数时 Map 的 key,然后通过 #{key} 取出传入的参数值,此方式也被称为 命名参数,例如:

1
2
3
4
5
public interface EmployeeMapper {

public Employee getEmpByIdAndEmailByAnnotation(@Param("id") Long id, @Param("email") String email);

}
1
2
3
4
5
6
7
8
9
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<select id="getEmpByIdAndEmailByAnnotation" resultType="com.clay.mybatis.bean.Employee">
select id, last_name as lastName, gender, email
from t_employee
where id = #{id} and email = #{email}
</select>

</mapper>

提示

当传入多个参数时,建议使用 @Param 注解,这样会使 SQL 映射文件的可读性更强。

使用 POJO 传递参数

如果有多个参数正好是业务逻辑的数据模型,那么还可以直接传入 POJO 对象,然后通过 #{属性名} 取出传入的 POJO 的属性值,例如:

1
2
3
4
5
public interface EmployeeMapper {

public boolean addEmp(Employee employee);

}
1
2
3
4
5
6
7
8
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<insert id="addEmp" parameterType="com.clay.mybatis.bean.Employee" useGeneratedKeys="true" keyProperty="id">
insert into t_employee (last_name, gender, email)
values(#{lastName}, #{gender}, #{email})
</insert>

</mapper>
使用 Map 传递参数

如果有多个参数且都不是业务模型中的数据,即没有 POJO 对象,为了开发方便,还可以直接传入 Map 对象,然后通过 #{key} 取出传入的参数值,例如:

1
2
3
4
5
public interface EmployeeMapper {

public Employee getEmpByIdAndEmailByMap(Map<String, Object> params);

}
1
2
3
4
5
6
7
8
9
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<select id="getEmpByIdAndEmailByMap" resultType="com.clay.mybatis.bean.Employee">
select id, last_name as lastName, gender, email
from t_employee
where id = #{id} and email = #{email}
</select>

</mapper>

提示

如果有多个参数且都不是业务模型中的数据,但是会经常使用,此时不建议使用 Map 来封装参数,而是推荐编写 DTO(Data Transfer Object)数据传输对象来封装多个参数。

使用 List 传递参数
  • 如果传入的参数是 Collection(List、Set) 集合类型或者是数组类型,MyBatis 会做特殊处理,也就是会把传入的集合对象或者数组封装在 Map 中。
  • 如果传入的参数是 Collection(List、Set) 类型,则可以统一使用 arg0 ... argN 作为 key 取出参数值。
  • 如果传入的是单参数且参数是 List 类型,则还可以使用 collection 或者 list 作为 key 取出参数值。
  • 如果传入的是单参数且参数是数组类型,则可以使用 array 作为 key 取出参数值。
1
2
Java 参数:public Employee getEmpById(List<Integer> ids);
SQL 取值:#{arg0[0]/collection[0]/list[0]} ==> 取出第一个ID的值
1
2
Java 参数:public Employee getEmpById(Long[] ids);
SQL 取值:#{array[0]} ==> 取出第一个ID的值
其他参数传递方式
1
2
Java Java 参数:public Employee getEmp(@Param("id")Integer id, String lastName);
SQL 取值:id ==> #{id/param1}, lastName ==> #{param2}
1
2
Java 参数:public Employee getEmp(Integer id, @Param("emp")Employee emp);
SQL 取值:id ==> #{param1}, lastName ==> #{param2.lastName/emp.lastName}

内置参数

MyBatis 提供了两个内置参数,分别是 _parameter_databaseId,两者的使用说明如下:

  • _parameter:代表所有参数
    • 单个参数:_parameter 则代表这个参数
    • 多个参数:多个参数会被 MyBatis 封装为一个 Map,而 _parameter 则代表这个 Map
  • _databaseId:如果在 MyBatis 的全局配置文件中配置了 databaseIdProvider 标签(数据库厂商标识),那么 _databaseId 就代表当前数据库的别名
1
2
3
4
5
6
7
8
9
<configuration>
<!-- 数据库厂商标识 -->
<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver" />
<property name="DB2" value="db2" />
<property name="MySQL" value="mysql" />
<property name="Oracle" value="oracle" />
</databaseIdProvider>
</configuration>
1
2
3
4
5
public interface EmployeeMapper {

public List<Employee> getEmpByInnerParameter(Employee employee);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<mapper namespace="com.clay.mybatis.dao.EmployeeMapper">

<select id="getEmpByInnerParameter" resultType="com.clay.mybatis.bean.Employee">
<!-- 使用内置参数判断数据库类型 -->
<if test="_databaseId == 'mysql'">
select * from t_employee
<!-- 使用内置参数判断传递进来的参数是否为NULL -->
<if test="_parameter != null">
where email = #{_parameter.email}
</if>
</if>
<if test="_databaseId == 'oracle'">
select * from employees
</if>
</select>

</mapper>

$ 与 # 符取值区别

两者的相同点:

  • #{}:可以获取 Map 中的值或者 POJO 对象属性的值
  • ${}:可以获取 Map 中的值或者 POJO 对象属性的值

两者的不同点:

  • #{}:是以预编译(PreparedStatement)的形式,将参数设置到 SQL 语句中,可以有效防止 SQL 注入,大多情况下都应该使用 #{} 取参数值
  • ${}:取出的值是直接拼接在 SQL 语句中,存在 SQL 注入的安全问题
  • 在原生 JDBC 不支持使用 ? 占位符的地方,只能使用 ${} 进行取值,比如分表、排序
1
2
3
4
5
// 按照年份分表
select * from ${year}_salary where xxx;

// 排序
select * from tbl_employee order by ${f_name} ${order}
  • #{} 可以指定一个额外的属性,包括 javaType、jdbcType、mode、numericScale、resultMap、typeHandler、jdbcTypeName
1
2
#{property, javaType=int, jdbcType=NUMERIC}
#{height, javaType=double, jdbcType=NUMERIC, numericScale=2}

提示

  1. MyBatis 对所有的 null 默认都映射为原生 Jdbc 的 OTHER 类型(无效的类型)
  2. jdbcType 通常需要在某种特定的条件下被设置,例如在数据为 null 的时候,有些数据库可能不能识别 MyBatis 对 null 的默认处理(将 null 映射为原生 Jdbc 的 OTHER 类型),比如 Oracle 不支持
  3. 解决 Oracle 对 null 值的兼容处理,可以使用 #{height, jdbcType=NULL} 指定 jdbcTypeNULL,或者在 MyBatis 的全局配置文件中配置 <setting name="jdbcTypeForNull" value="NULL"/>

参数处理源码分析

MyBatis 参数处理的核心代码在 ParamNameResolver 类里,最终结果是将传入的参数封装成 Map 对象,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/**
* Copyright 2009-2020 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.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
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 {

public static final String GENERIC_NAME_PREFIX = "param";

private final boolean useActualParamName;

/**
* <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) -&gt; {{0, "M"}, {1, "N"}}</li>
* <li>aMethod(int a, int b) -&gt; {{0, "0"}, {1, "1"}}</li>
* <li>aMethod(int a, RowBounds rb, int b) -&gt; {{0, "0"}, {2, "1"}}</li>
* </ul>
*/
private final SortedMap<Integer, String> names;

private boolean hasParamAnnotation;

public ParamNameResolver(Configuration config, Method method) {
this.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 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 (useActualParamName) {
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) {
return ParamNameUtil.getParamNames(method).get(paramIndex);
}

private static boolean isSpecialParameter(Class<?> clazz) {
return RowBounds.class.isAssignableFrom(clazz) || ResultHandler.class.isAssignableFrom(clazz);
}

/**
* Returns parameter names referenced by SQL providers.
*
* @return the names
*/
public String[] getNames() {
return names.values().toArray(new String[0]);
}

/**
* <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;
} else if (!hasParamAnnotation && paramCount == 1) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
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 + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}

/**
* 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) {
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;
} else 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;
}

}