MyBatis框架的使用及源码分析(八) MapperMethod
从 <MyBatis框架中Mapper映射配置的使用及原理解析(七) MapperProxy,MapperProxyFactory> 文中,我们知道Mapper,通过MapperProxy代理类执行他的接口方法,当mapper方法被调用的时候对应的MapperProxy会生成相应的MapperMethod并且会缓存起来,这样当多次调用同一个mapper方法时候只会生成一个MapperMethod,提高了时间和内存效率:
//这里会拦截Mapper接口的所有方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) { //如果是Object中定义的方法,直接执行。如toString(),hashCode()等
try {
return method.invoke(this, args);//
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method); //其他Mapper接口定义的方法交由mapperMethod来执行
return mapperMethod.execute(sqlSession, args);
}
最后2句关键,我们执行所调用Mapper的每一个接口方法,最后返回的是MapperMethod.execute方法。每一个MapperMethod对应了一个mapper文件中配置的一个sql语句或FLUSH配置,对应的sql语句通过mapper对应的class文件名+方法名从Configuration对象中获得。
我们看一下MapperMethod的源码:
package org.apache.ibatis.binding; import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession; import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.*;
/**
* MapperMethod代理Mapper所有方法
*/
public class MapperMethod {
//一个内部封 封装了SQL标签的类型 insert update delete select
private final SqlCommand command;
//一个内部类 封装了方法的参数信息 返回类型信息等
private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}
/**
* 这个方法是对SqlSession的包装,对应insert、delete、update、select四种操作
*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;//返回结果
//INSERT操作
if (SqlCommandType.INSERT == command.getType()) {
//处理参数
Object param = method.convertArgsToSqlCommandParam(args);
//调用sqlSession的insert方法
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
//UPDATE操作 同上
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
//DELETE操作 同上
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
//如果返回void 并且参数有resultHandler ,则调用 void select(String statement, Object parameter, ResultHandler handler);方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//如果返回多行结果,executeForMany这个方法调用 <E> List<E> selectList(String statement, Object parameter);
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//如果返回类型是MAP 则调用executeForMap方法
result = executeForMap(sqlSession, args);
} else {
//否则就是查询单个对象
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
//接口方法没有和sql命令绑定
throw new BindingException("Unknown execution method for: " + command.getName());
}
//如果返回值为空 并且方法返回值类型是基础类型 并且不是VOID 则抛出异常
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;
} private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long) rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = (rowCount > 0);
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
} private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
if (void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
+ " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
//返回多行结果 调用sqlSession.selectList方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
//如果参数含有rowBounds则调用分页的查询
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
//没有分页则调用普通查询
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
} private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
Object collection = config.getObjectFactory().create(method.getReturnType());
MetaObject metaObject = config.newMetaObject(collection);
metaObject.addAll(list);
return collection;
} @SuppressWarnings("unchecked")
private <E> E[] convertToArray(List<E> list) {
E[] array = (E[]) Array.newInstance(method.getReturnType().getComponentType(), list.size());
array = list.toArray(array);
return array;
} private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
}
return result;
} public static class ParamMap<V> extends HashMap<String, V> { private static final long serialVersionUID = -2212268410512043556L; @Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
}
return super.get(key);
} }
//封装了具体执行的动作
public static class SqlCommand {
//xml标签的id
private final String name;
//insert update delete select的具体类型
private final SqlCommandType type; public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) throws BindingException {
//拿到全名 比如 org.mybatis.example.UserMapper.selectByPrimaryKey
String statementName = mapperInterface.getName() + "." + method.getName();
//MappedStatement对象,封装一个Mapper接口对应的sql操作
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
//从Configuration对象查找是否有这个方法的全限定名称,如果有则根据方法的全限定名称获取MappedStatement
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
//如果没有在Configuration对象中找到这个方法,则向上父类中获取全限定方法名
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
//这个ms.getId,其实就是我们在mapper.xml配置文件中配置一条sql语句设置的id属性的值
name = ms.getId();
//sql的类型(insert、update、delete、select)
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
//判断SQL标签类型 未知就抛异常
throw new BindingException("Unknown execution method for: " + name);
}
} public String getName() {
return name;
} public SqlCommandType getType() {
return type;
}
}
/**
* 方法签名,封装了接口当中方法的 参数类型 返回值类型 等信息
*/
public static class MethodSignature { private final boolean returnsMany;//是否返回多条结果
private final boolean returnsMap; //返回值是否是MAP
private final boolean returnsVoid;//返回值是否是VOID
private final Class<?> returnType; //返回值类型
private final String mapKey;
private final Integer resultHandlerIndex;//resultHandler类型参数的位置
private final Integer rowBoundsIndex; //rowBound类型参数的位置
private final SortedMap<Integer, String> params;//用来存放参数信息
private final boolean hasNamedParameters; //是否存在命名参数 public MethodSignature(Configuration configuration, Method method) throws BindingException {
this.returnType = method.getReturnType();
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
this.hasNamedParameters = hasNamedParams(method);
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}
/**
* 创建SqlSession对象需要传递的参数逻辑
* args是用户mapper所传递的方法参数列表, 如果方法没有参数,则返回null.
* 如果方法只包含一个参数并且不包含命名参数, 则返回传递的参数值。
* 如果包含多个参数或包含命名参数,则返回包含名字和对应值的map对象、
*/
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasNamedParameters && paramCount == 1) {
return args[params.keySet().iterator().next()];
} else {
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
} public boolean hasRowBounds() {
return (rowBoundsIndex != null);
} public RowBounds extractRowBounds(Object[] args) {
return (hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null);
} public boolean hasResultHandler() {
return (resultHandlerIndex != null);
} public ResultHandler extractResultHandler(Object[] args) {
return (hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null);
} public String getMapKey() {
return mapKey;
} public Class<?> getReturnType() {
return returnType;
} public boolean returnsMany() {
return returnsMany;
} public boolean returnsMap() {
return returnsMap;
} public boolean returnsVoid() {
return returnsVoid;
} private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (paramType.isAssignableFrom(argTypes[i])) {
if (index == null) {
index = i;
} else {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
} private String getMapKey(Method method) {
String mapKey = null;
if (Map.class.isAssignableFrom(method.getReturnType())) {
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
} private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
String paramName = String.valueOf(params.size());
if (hasNamedParameters) {
paramName = getParamNameFromAnnotation(method, i, paramName);
}
params.put(i, paramName);
}
}
return params;
} private String getParamNameFromAnnotation(Method method, int i, String paramName) {
final Object[] paramAnnos = method.getParameterAnnotations()[i];
for (Object paramAnno : paramAnnos) {
if (paramAnno instanceof Param) {
paramName = ((Param) paramAnno).value();
}
}
return paramName;
} private boolean hasNamedParams(Method method) {
boolean hasNamedParams = false;
final Object[][] paramAnnos = method.getParameterAnnotations();
for (Object[] paramAnno : paramAnnos) {
for (Object aParamAnno : paramAnno) {
if (aParamAnno instanceof Param) {
hasNamedParams = true;
break;
}
}
}
return hasNamedParams;
} } }
当执行MapperMethod的execute方法的时候,根据当前MapperMethod对应的mapper配置会执行Session的insert, update, delete, select, selectList, selectMap, selectCursor, selectOne或flushStatements方法。
具体执行Session对象的方法对照如下:
Mapper节点 | SqlSession方法 |
insert | insert |
update | update |
delete | delete |
select | select: 方法返回void,并且包含resultHandler配置 |
select | selectList:方法返回数组或Collection子类 |
select | selectMap: 存在MapKey注解 |
select | selectCursor: 方法返回Cursor |
select | selectOne 其它 |
flush | Flush注解 |
MyBatis框架的使用及源码分析(八) MapperMethod的更多相关文章
- MyBatis框架的使用及源码分析(九) Executor
从<MyBatis框架的使用及源码分析(八) MapperMethod>文中我们知道执行Mapper的每一个接口方法,最后调用的是MapperMethod.execute方法.而当执行Ma ...
- MyBatis框架的使用及源码分析(十一) StatementHandler
我们回忆一下<MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor> , 这4个Ex ...
- MyBatis框架的使用及源码分析(六) MapperRegistry
我们先Mapper接口的调用方式,见<MyBatis框架中Mapper映射配置的使用及原理解析(一) 配置与使用>的示例: public void findUserById() { Sql ...
- MyBatis框架的使用及源码分析(五) DefaultSqlSessionFactory和DefaultSqlSession
我们回顾<MyBatis框架中Mapper映射配置的使用及原理解析(一) 配置与使用> 一文的示例 private static SqlSessionFactory getSessionF ...
- MyBatis框架的使用及源码分析(四) 解析Mapper接口映射xml文件
在<MyBatis框架中Mapper映射配置的使用及原理解析(二) 配置篇 SqlSessionFactoryBuilder,XMLConfigBuilder> 一文中,我们知道mybat ...
- MyBatis框架的使用及源码分析(二) 配置篇 SqlSessionFactoryBuilder,XMLConfigBuilder
在 <MyBatis框架中Mapper映射配置的使用及原理解析(一) 配置与使用> 的demo中看到了SessionFactory的创建过程: SqlSessionFactory sess ...
- MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor
Executor分成两大类,一类是CacheExecutor,另一类是普通Executor. 普通类又分为: ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情.它为每个语句的执行 ...
- MyBatis框架的使用及源码分析(七) MapperProxy,MapperProxyFactory
从上文<MyBatis框架中Mapper映射配置的使用及原理解析(六) MapperRegistry> 中我们知道DefaultSqlSession的getMapper方法,最后是通过Ma ...
- MyBatis框架的使用及源码分析(三) 配置篇 Configuration
从上文<MyBatis框架中Mapper映射配置的使用及原理解析(二) 配置篇 SqlSessionFactoryBuilder,XMLConfigBuilder> 我们知道XMLConf ...
随机推荐
- 互评Alpha版本——Thunder团队
基于NABCD评论作品 Hello World! :http://www.cnblogs.com/120626fj/p/7807544.html 欢迎来怼 :http://www.cnblogs.co ...
- lintcode-34-N皇后问题 II
34-N皇后问题 II 根据n皇后问题,现在返回n皇后不同的解决方案的数量而不是具体的放置布局. 样例 比如n=4,存在2种解决方案 标签 递归 思路 参考http://www.cnblogs.com ...
- iOS开发解决 jsonModel 属性跟系统的重复
-(id)initWithDic:(NSDictionary *)dic { if (self = [super init]) { [self setValuesForKeysWithDictiona ...
- 关于设置Visaul Studio 2010 代码编辑界面背景的方法
1.打开代码编辑界面: 2.找到工具--选项: 3.打开选项后选中纯文本--项背景色: 4.点击自定义,找到自己需要的颜色: [注]: “项前景色”即代码的颜色: “项背景色”即背景颜色. 设置好后, ...
- 在intelllij中debug的时候使用log4j输出
一般在本地调试的时候,在控制台打印输出都会使用system.out.print,但是在线上运行的时候一般都是使用log4j进行日志输出. 那么在编写代码的时候,又不想写两份,直接写LOG是常规动作. ...
- JavaScript中setInterval常见的问题(setInterval第一个参数加引号与不加引号区别)
- [剑指Offer] 46.孩子们的游戏
题目描述 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此.HF作为牛客的资深元老,自然也准备了一些小游戏.其中,有个游戏是这样的:首先,让小朋友们围成一个大圈.然后,他随机指 ...
- 【bzoj1370】[Baltic2003]Gang团伙 并查集
题目描述 在某城市里住着n个人,任何两个认识的人不是朋友就是敌人,而且满足: 1. 我朋友的朋友是我的朋友: 2. 我敌人的敌人是我的朋友: 所有是朋友的人组成一个团伙.告诉你关于这n个人的m条信息, ...
- hdu 1068 Girls and Boys (二分匹配)
Girls and Boys Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) ...
- Kingdom and its Cities - CF613D
Meanwhile, the kingdom of K is getting ready for the marriage of the King's daughter. However, in or ...