一、输入参数源码分析
1、参数解析器解析参数(得到一个Map集合 names,默认是{arg0,arg1,arg2....},如果某一个位置使用了@Param注解,那么该位置的argx会被注解的值替代)
public ParamNameResolver(Configuration config, Method method) {
// 批量获取参数类型,我这里参数类型分别是Integer、String、String
final Class<?>[] paramTypes = method.getParameterTypes();
// 批量获取标注了@Param注解的参数,用一个二维数组封装,由于我只在id上标注了一个@Param("userId")
// 二维数组的长度与传入的参数相同,有几个参数二维数组的长度便是多少,标注了@Param注解的值用一维数组封装
// 所以最终的二维数组如下:[Annotation[1],Annotation[0],Annotation[0],]
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
// 获取二维数组的长度,由于传入了三个参数,所以长度为3
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的值为 userId
name = ((Param) annotation).value();
break;
}
}
// 如果不是使用@Param注解标注的参数,并且传入了实际参数,那么根据paramIndex的值去获取对应的值{arg0,arg1,arg2}
// 例如我们这里第二个参数使用的是 userName,那么它对应的就是 name就是arg1,第三个参数password对应的就是 arg2
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就是{0:userId,1:arg1,2:arg2}
map.put(paramIndex, name);
}
// 通过Collections工具类将map转换成一个SortedMap names={0:userId,1:arg1,2:arg2}
names = Collections.unmodifiableSortedMap(map);
}
2、通过动态代理获取xxxMapper接口的代理类,然后通过invoke(...)执行目标方法,Object[] args 数组中封装的是实际传入的参数[实际参数1,实际参数2,实际参数3....]
// 动态代理执行invoke方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (method.isDefault()) { if (privateLookupInMethod == null) { return invokeDefaultMethodJava8(proxy, method, args); } else { return invokeDefaultMethodJava9(proxy, method, args); } } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); // 关键方法,此处的args就是实际传入的参数 Object[2,"李四","24"] return mapperMethod.execute(sqlSession, args); }
3、根据操作的类型来选择不同的操作方法
// 通过command.getType()方法来判断当前类型是INSERT/UPDATE/DELETE/SELECT
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);
// 是否使用@MapKey注解
} 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;
}
4、将实际参数转换成Sql命令参数
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
5、获取最终的参数集合
public Object getNamedParams(Object[] args) {
// 这里面的names就是我们通过参数解析器获得的SortedMap names={0:userId,1:arg1,2:arg2}
// 此处的args就是实际传入的参数 args = Object[2,"李四","24"]
final int paramCount = names.size();
// 如果没有传入实际参数,或者参数的名称个数为0,直接返回null
if (args == null || paramCount == 0) {
return null;
// 如果没有使用@Parma注解并且只有一个参数,那么直接返回第一个参数
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
// names={0:userId,1:arg1,2:arg2} args = Object[2,"李四","24"]
// 遍历names,并且将相应的数据存入Map集合param中,存入的规则如下
// param = {userId:2,arg1:"李四",arg2:"24"}
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// GENERIC_NAME_PREFIX的值为常量 param
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// 如果names中不包含param1、param2...等等数据,那么集合param中继续存入一份数据
// {param1:2,param2:"李四",param3:"24"}
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
// 最终的Map集合 param={userId:2,arg1:"李四",arg2:"24",param1:2,param2:"李四",param3:"24"}
// 相当于是对传入的参数使用了两套保存机制
return param;
}
}
}
二、实际案例结合上述源码
1、UserMapper接口
public interface UserMapper {
// 根据id、userName、password三个条件查询用户信息
public abstract User queryUserByThreeConditions(@Param("userId") Integer id, String userName, String password);
}
2、UserMapper.xml
根据下面的源码分析,可以得出如下结论,当传入多个参数的时候,如果没有使用@Param注解对参数进行标注,那么param={arg0:2,arg1:"李四",arg2:"24",param1:2,param2:"李四",param3:"24"},如果某个参数使用了@Param注解的话,那么对应的位置的 argx 就会被@Param("value")中的value值所替代,
所以我们这个例子中param={userId:2,arg1:"李四",arg2:"24",param1:2,param2:"李四",param3:"24"},只要能确保传入的最终参数是{2,"李四","24"},你可以使用各种各样的方式去param这个集合中取取值,例如下面的几种方式都是可以的
<!--根据id、userName、password三个条件查询用户信息-->
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
WHERE ID=#{param1} AND USER_NAME=#{param2} AND PASSWORD=#{param3}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
WHERE ID=#{userId} AND USER_NAME=#{arg1} AND PASSWORD=#{arg2}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
WHERE ID=#{userId} AND USER_NAME=#{param2} AND PASSWORD=#{param3}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
WHERE ID=#{param1} AND USER_NAME=#{arg1} AND PASSWORD=#{arg2}
</select>
3、测试类
public class TestMybatis {
@Test
public void test01() throws IOException {
SqlSession session = TestMybatis.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserByThreeConditions(2, "李四", "24");
System.out.println(user);
}
}
三、输入参数
1、单个参数
01、传入基本类型数据
<!--根据主键ID查询用户信息-->
// UserMapper接口
public abstract User queryUserById(Integer id);
// UserMapper.xml
<select id="queryUserById" resultType="com.mybatis.entity.User">
// 取值的时候可以使用#{任意字符}获得传入的参数
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user WHERE ID=#{xxx}
</select>
02、传入POJO类型
<!--根据id和userName查询用户信息-->
// UserMapper接口
public abstract User queryUserByIdAnduserName(User user);
// UserMapper.xml
<select id="queryUserByIdAnduserName" resultType="com.mybatis.entity.User">
// 对于传入的是POJO类型的参数,取值的时候需要使用#{POJO的属性名}来取值
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
WHERE ID=#{id} AND USER_NAME=#{userName}
</select>
2、多个参数(这种情况下可以配合@Param("value")注解,可以做到见名知意)
<!--根据id、userName、password三个条件查询用户信息-->
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
WHERE ID=#{param1} AND USER_NAME=#{param2} AND PASSWORD=#{param3}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
WHERE ID=#{userId} AND USER_NAME=#{arg1} AND PASSWORD=#{arg2}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
WHERE ID=#{userId} AND USER_NAME=#{param2} AND PASSWORD=#{param3}
</select>
<select id="queryUserByThreeConditions" resultType="com.mybatis.entity.User">
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
WHERE ID=#{param1} AND USER_NAME=#{arg1} AND PASSWORD=#{arg2}
</select>
3、传入Map集合
传入Map集合(mybatis本来就是要把输入的参数转化为map存起来,既然你已经传入了一个map集合,直接通过键取值就可以了)
map:{"id":1,"password":23}
public abstract User queryUserByIdAndPassword(Map<String,Object> map);
// 这里的id、password对应的是传入进来的Map集合的key
<select id="queryUserByIdAndPassword" resultType="com.mybatis.entity.User">
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM user
WHERE ID=#{id} AND PASSWORD=#{password}
</select>
4、传入Integer类型数组
//根据多个用户id查询出多条用户记录
public abstract List<User> queryUsersByIds(Integer[] ids);
<!--根据多个用户id查询出多条用户记录-->
<select id="queryUsersByIds" resultType="com.mybatis.domain.User">
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM tbl_user
<where>
ID in
<foreach collection="array" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</where>
</select>
5、传入List集合
//根据多个用户id查询出多条用户记录
public abstract List<User> queryUsersByIds(List<Integer> ids);
<!--根据多个用户id查询出多条用户记录-->
<select id="queryUsersByIds" resultType="com.mybatis.domain.User">
SELECT ID,USER_NAME,PASSWORD,USER_INFORMATION FROM tbl_user
<where>
ID in
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</where>
</select>
四、输出参数
1、resultType,返回值类型为实体类对象:自动封装,将数据库中的数据自动封装到javaBean中
// 根据用户EmployeeId查询用户信息
public abstract Employee queryEmployeeByEmployeeId(Employee employee);
<select id="queryEmployeeByEmployeeId" resultType="com.mybatis.entity.Employee">
SELECT
e.EMPLOYEE_ID,
e.EMPLOYEE_NAME,
e.EMPLOYEE_PASSWORD,
e.EMPLOYEE_AGE,
e.DEPARTMENT_ID
FROM
employee e
WHERE
e.EMPLOYEE_ID=#{employeeId}
</select>
// 测试结果
Employee{employeeId=3, employeeName='王五', employeePassword='789', employeeAge='25', departmentId=3}
2、resultType,返回值类型为map
// 根据用户EmployeeId查询用户信息
public abstract Map<String,Object> queryEmployeeByEmployeeId(Employee employee);
<select id="queryEmployeeByEmployeeId" resultType="java.util.Map">
SELECT
e.EMPLOYEE_ID,
e.EMPLOYEE_NAME,
e.EMPLOYEE_PASSWORD,
e.EMPLOYEE_AGE,
e.DEPARTMENT_ID
FROM
employee e
WHERE
e.EMPLOYEE_ID=#{employeeId}
</select>
// 测试结果
{EMPLOYEE_ID=3, EMPLOYEE_AGE=25, DEPARTMENT_ID=3, EMPLOYEE_NAME=王五, EMPLOYEE_PASSWORD=789}
3、如果查询的结果是多条记录,而又指定返回值为map集合,这个时候就不能使用mybatis的自动封装,这个时候要为多条记录指定主键,使用@MapKey("数据库中的列名")
// EMPLOYEE_ID对应的是数据库的字段
@MapKey("EMPLOYEE_ID")
public abstract Map<String,Object> queryEmployeeByEmployeeId(Employee employee);
// 根据employeeAge模糊查询用户信息
<select id="queryEmployeeByEmployeeId" resultType="java.util.Map">
SELECT
e.EMPLOYEE_ID,
e.EMPLOYEE_NAME,
e.EMPLOYEE_PASSWORD,
e.EMPLOYEE_AGE,
e.DEPARTMENT_ID
FROM
employee e
WHERE
e.EMPLOYEE_AGE
LIKE
"%"#{employeeAge}"%"
</select>
// 测试结果
{
1={EMPLOYEE_ID=1, EMPLOYEE_AGE=23, DEPARTMENT_ID=1, EMPLOYEE_NAME=张三, EMPLOYEE_PASSWORD=123},
2={EMPLOYEE_ID=2, EMPLOYEE_AGE=24, DEPARTMENT_ID=2, EMPLOYEE_NAME=李四, EMPLOYEE_PASSWORD=456},
3={EMPLOYEE_ID=3, EMPLOYEE_AGE=25, DEPARTMENT_ID=3, EMPLOYEE_NAME=王五, EMPLOYEE_PASSWORD=789},
4={EMPLOYEE_ID=4, EMPLOYEE_AGE=26, DEPARTMENT_ID=4, EMPLOYEE_NAME=赵六, EMPLOYEE_PASSWORD=1123},
5={EMPLOYEE_ID=5, EMPLOYEE_AGE=27, DEPARTMENT_ID=1, EMPLOYEE_NAME=田七, EMPLOYEE_PASSWORD=123579}
}
4、返回值类型为resultMap:支持手动的封装(多对一)
// 实体类Employee,省略set/get/toString方法
public class Employee {
private Integer employeeId;
private String employeeName;
private String employeePassword;
private String employeeAge;
private Department department;
}
// 实体类Department,省略set/get/toString方法
public class Department {
private Integer departmentId;
private String description;
private String location;
}
// EmployeeMapper接口
public interface EmployeeMapper {
// 根据用户EmployeeId查询用户信息
public abstract Employee queryEmployeeByEmployeeId(Integer id);
}
// EmployeeMapper的全包类路径
<mapper namespace="com.mybatis.dao.EmployeeMapper">
// 这里的emp可以是任意值,但是一定要和resultMap的值相同
<resultMap id="emp" type="com.mybatis.entity.Employee">
// 主键策略
<id property="employeeId" column="EMPLOYEE_ID" javaType="int" jdbcType="INTEGER"></id>
<result property="employeeName" column="EMPLOYEE_NAME" javaType="string" jdbcType="VARCHAR"></result>
<result property="employeePassword" column="EMPLOYEE_PASSWORD" javaType="string" jdbcType="VARCHAR"></result>
<result property="employeeAge" column="EMPLOYEE_AGE" javaType="string" jdbcType="VARCHAR"></result>
// 由于Employee实体类中有Department属性,这里的department对应的是属性名,javaType对应Department实体类的全包类路径
<association property="department" javaType="com.mybatis.entity.Department">
// 主键策略
<id property="departmentId" column="DEPARTMENT_ID" javaType="int" jdbcType="INTEGER"></id>
<result property="description" column="DESCRIPTION" javaType="string" jdbcType="VARCHAR"></result>
<result property="location" column="LOCATION" javaType="string" jdbcType="VARCHAR"></result>
</association>
</resultMap>
<select id="queryEmployeeByEmployeeId" resultMap="emp">
SELECT
e.EMPLOYEE_ID,
e.EMPLOYEE_NAME,
e.EMPLOYEE_PASSWORD,
e.EMPLOYEE_AGE,
d.DEPARTMENT_ID,
d.DESCRIPTION,
d.LOCATION
FROM employee e
LEFT JOIN
department d
ON
e.DEPARTMENT_ID=D.DEPARTMENT_ID
WHERE e. EMPLOYEE_ID=#{employeeId}
</select>
</mapper>
// 测试结果
Employee{
employeeId=1,
employeeName='张三',
employeePassword='123',
employeeAge='23',
department=Department{departmentId=1, description='市场部', location='北京'}
}
5、返回值类型为resultMap:支持手动的封装(一对多)
// 实体类Employee,省略set/get/toString方法
public class Employee {
private Integer employeeId;
private String employeeName;
private String employeePassword;
private String employeeAge;
private Integer departmentId;
}
// 实体类Department,省略set/get/toString方法
public class Department {
private Integer departmentId;
private String description;
private String location;
private List<Employee> employees;
}
// 查询部门编号为1的所有所有员工信息
public abstract Department queryAllByDepartmentId(Integer id);
<mapper namespace="com.mybatis.dao.DepartmentMapper">
// dep任意,但是一定要和resultMap的值相同
<resultMap id="dep" type="com.mybatis.entity.Department">
<id property="departmentId" column="DEPARTMENT_ID" javaType="int" jdbcType="INTEGER"></id>
<result property="description" column="DESCRIPTION" javaType="string" jdbcType="VARCHAR"></result>
<result property="location" column="LOCATION" javaType="string" jdbcType="VARCHAR"></result>
// Department中有一个List<Employee>集合,使用ofType引入Employee对象
<collection property="employees" ofType="com.mybatis.entity.Employee">
<id property="employeeId" column="EMPLOYEE_ID" javaType="int" jdbcType="INTEGER"></id>
<result property="employeeName" column="EMPLOYEE_NAME" javaType="string" jdbcType="VARCHAR"></result>
<result property="employeePassword" column="EMPLOYEE_PASSWORD" javaType="string" jdbcType="VARCHAR"></result>
<result property="employeeAge" column="EMPLOYEE_AGE" javaType="string" jdbcType="VARCHAR"></result>
<result property="departmentId" column="DEPARTMENT_ID" javaType="int" jdbcType="INTEGER"></result>
</collection>
</resultMap>
<select id="queryAllByDepartmentId" resultMap="dep">
SELECT
d.DEPARTMENT_ID,
d.DESCRIPTION,
d.LOCATION,
e.EMPLOYEE_ID,
e.EMPLOYEE_NAME,
e.EMPLOYEE_PASSWORD,
e.EMPLOYEE_AGE
FROM department d
LEFT JOIN
employee e
ON
d.DEPARTMENT_ID=e.DEPARTMENT_ID
WHERE d.DEPARTMENT_ID=#{departmentId}
</select>
</mapper>
// 测试结果
Department{
departmentId=1,
description='市场部',
location='北京',
employees=
[
Employee{employeeId=1, employeeName='张三', employeePassword='123', employeeAge='23', departmentId=1},
Employee{employeeId=5, employeeName='田七', employeePassword='123579', employeeAge='27', departmentId=1}
]
}