笔记大纲
- 参数传递方式
- 单个普通类型参数
- 多个普通类型参数
- 命名参数(参数少建议使用注解)
- POJO(常用)
- Map(参数多建议使用)
- Collection/Array
- 参数传递源代码分析(重点)
1.参数传递方式
我们在映射文件执行SQL语句时,需要传入对应的参数,满足语句时才会返回相应的值,就传入的参数类型、个数进行了以下情况的列举与分析。
1.1.单个普通类型参数
如果参数是基本数据类型、包装类型、字符串类型等,MyBatis可直接使用这个参数,不需要任何处理。
取值格式:#{随便写}
测试用例场景:根据id条件查询员工信息。
(1)mapper接口(单个参数查询)
(2)映射文件mybatis-conf.xml
(3)Junit测试
(4)输出符合预期结果
1.2.多个普通类型参数
如果参数有多个时,会被MyBatis重新包装成一个Map传入,Map的key是param1,param2,或者是0,1…,对应的value就是参数的值。与单个普通类型参数传入的参数有所差异!!!
测试用例场景:根据id和lastname条件查询员工信息。
(1)mapper接口(多个参数查询)
(2)映射文件mybatis-conf.xml
(3)Junit测试
(4)不符合预期结果(报错)
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [0, 1, param1, param2]
### Cause: org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [0, 1, param1, param2]
(5)(修改)映射文件mybatis-conf.xml中传入参数
(6)(修改后)符合预期结果
1.3.命名参数(参数少建议使用注解)
当参数添加后位置发生改变,传入的key是0,1,(上述1.2)就不能满足要求!应该采用命名参数的方式。
为传入的参数使用注解@Param
起名字,MyBatis会将这些参数封装到一个map中,key就是我们要指定的名字!有注解@Param
,key
值就不是0
,1
方式取值,而是通过Param1
、Param2
方式取值,这里需要注意细节。
(1)mapper接口(多个参数查询)
(2)映射文件mybatis-conf.xml
(3)Junit测试–测试通过
注意:此时传入的方式是
0
,1
参数值,就会报错!
select id,last_name,email,gender from employee_table where id =#{0} and last_name =#{1}
<org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.codinglin.mybatis.mapper.EmployeeMapper.selectEmployeeByIdAndName2
小结
1.2、1.3的传入参数的底层实现就是Map集合,如果是集合,作为开发人员,我们自己是可以自己封装!
1.4.POJO(常用)
数据传输对象(DTO:Data Transfer Object)
当参数属于业务POJO
时,就可以直接传递POJO取值。注意:如果参数有多个且经常被使用,一般会把参数参数封装到具体的对象进行传递。
取值格式:#{JavaBean的属性名}
1.5.Map(参数多建议使用)
如果参数有多个时,直接封装到一个map中,直接进行传递。
取值格式:#{Map的key}
(1)mapper接口(多个参数查询)
(2)映射文件mybatis-conf.xml
(3)Junit测试
(4)符合预期结果
1.6.Collection/Array
同上封装到一个map中,Collection对应的key是collection,Arrayn对应的key是array;
如果是List集合,key还可以是list,MyBatis底层会封装Map,其常见规则:
key | value | |
---|---|---|
List | collection/list | 实际传递的参数值 |
Set | collection | 实际传递的参数值 |
Array | array | 实际传递的参数值 |
2.参数传递源代码分析
我选取了命名参数的传递方式进行了源代码的跟踪与分析!
mapper接口
mapper映射文件
测试类方法
(1)如何获取动态代理对象?
导入源码包:mybatis-3-mybatis-3.4.1_source.zip
//获取接口的代理实现类的实例============加上断点
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
---------------------------------------------------------------------------------------
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this); //============F5进入方法
}
-----------------------------------------------------------------------------------------
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); //============F6运行下一行代码,单步调试
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);//=======单步调试截止,F5进入方法
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
-----------------------------------------------------------------------------------------
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);//============F6运行下一行代码,单步调试
return newInstance(mapperProxy);//============单步调试截止,F5进入方法
}
----------------------------------------------------------------------------------------
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);//============通过newProxyInstance()方法获取代理对象
}
(2)底层如何把参数存放到map中,进行封装?
//再此之前有自动装箱操作
//实现了InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
//在invoke()中调用了目标对象的方法,完成了代理的过程
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) { //F6运行下一行代码,单步调试
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
// arg是数组类型--Object[]
return mapperMethod.execute(sqlSession, args);//单步调试截止,F5进入方法
----------------------------------------------------------------------------------------
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {//F6运行下一行代码,单步调试
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://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 {
//转换为sql的命令参数
Object param = method.convertArgsToSqlCommandParam(args);//单步调试截止,F5进入方法
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);//F5进入方法
}
----------------------------------------------------------------------------------
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();//F6运行下一行代码,单步调试
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;
//map集合,这是底层帮我们封装好的!!!
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;
}
}
☝上述分享来源个人总结,如果分享对您有帮忙,希望您积极转载;如果您有不同的见解,希望您积极留言,让我们一起探讨,您的鼓励将是我前进道路上一份助力,非常感谢!我会不定时更新相关技术动态,同时我也会不断完善自己,提升技术,希望与君同成长同进步!
☞本人博客:https://coding0110lin.blog.csdn.net/ 欢迎转载,一起技术交流吧!