您的位置:首页 > 移动开发

mybatis源码解析(四)-Mapper方法调用过程

2018-01-31 18:50 916 查看
mybatis源码解析(一)-开篇

mybatis源码解析(二)-加载过程

mybatis源码解析(三)-SqlSession.selectOne类似方法调用过程

mybatis源码解析(四)-Mapper方法调用过程

mybatis源码解析(五)-mybatis如何实现的事务控制

mybatis源码解析(六)-配合spring-tx实现事务的原理

mybatis源码解析(七)-当mybatis一级缓存遇上spring

转载请标明出处:

http://blog.csdn.net/bingospunky/article/details/79220894

本文出自马彬彬的博客

上一篇博客描述了SqlSession的执行过程。但是我们使用mybatis时一般使用Mapper进行增删改查,这篇文章展示一下Mapper的方法是如何执行的。

使用Mapper进行增删改查分为两个步骤,第一步先获取Mapper对象,第二部调用Mapper的方法。

我们调用的方法对应的xxxMapper.xml的内容如下:

Code 1

<select id="selectById" resultMap="baseResultMap">
select
<include refid="baseColumnList"/>
from
table1
where id = #{id}
</select>


获取Mapper

获取Mapper的主要代码如下:

Code 2

org.apache.ibatis.binding.MapperProxyFactory

public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}


这段代码是MapperProxyFactory里的,MapperProxyFactory的作用就是来生成Mapper,一个Mapper对应一个MapperProxyFactory。

从代码里可以看到:我们获取的对象是通过JdbcProxy生成的,代理对象需要的三个参数:第一个不解释;第二个就是我们要获取的Mappe接口;第三个参数mapperProxy就比较关键了,它就是一个回调,当我们调用Mapper接口的方法时,会回调mapperProxy里的方法,我们需要在mapperProxy里实现回调方法,真正去操作数据库。关于mapperProxy,会在执行查询过程详细叙述。

调用Mapper的方法

由于我们获取的Mapper对象是JdbcProxy对象,在我们调用方法时,回调接口的方法会被调用,回调接口里的代码如下:

Code 3

org.apache.ibatis.binding.MapperProxy

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}


第9行通过Method获取MapperMethod,第10行调用MapperMethod.execute方法执行后面真正的sql。当我们获取MapperMethod时,如果是第一个获取的话,需要构造一个MapperMethod。下面我们从构造MapperMethod,和执行MapperMethod.execute两部分描述。

构造MapperMethod

构造MapperMethod的代码如下:

Code 4

org.apache.ibatis.binding.MapperMethod

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, method);
}


MapperMethod.SqlCommand比较简单,就包含这个Mapper方法对应的MappedStatement的Id和该方法对应的SqlCommandType,SqlCommandType有UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH这几个值。这里可以注意一点,当这个Mapper方法没有对应的MappedStatement且该方法没有被org.apache.ibatis.annotations.Flush注解标记时会抛异常。这很好理解,在xxxMapper.xml里没有对应的配置,所有这个方法没法执行。

MapperMethod.MethodSignature稍微麻烦一点,构造MapperMethod.MethodSignature的代码如下:

Code 5

org.apache.ibatis.binding.MapperMethod.MethodSignature

public MethodSignature(Configuration configuration, Method method) {
this.returnType = method.getReturnType();
this.returnsVoid = Void.TYPE.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.mapKey = this.getMapKey(method);
this.returnsMap = this.mapKey != null;
this.hasNamedParameters = this.hasNamedParams(method);
this.rowBoundsIndex = this.getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class);
this.params = Collections.unmodifiableSortedMap(this.getParams(method, this.hasNamedParameters));
}


第2行获取Mapper的方法返回值的类型。第4行获取返回类型是不是集合,比如数组或Collection接口都是集合,这个值后面分析会用到。第7行判断该方法的参数中是否包含了org.apache.ibatis.annotations.Param注解,这个值会影响实参的处理。第8行获取参数中RowBounds类型的位置,如果参数中包含多个RowBounds类型参数,也会抛异常。第9行与第8行类似,获取ResultHandler类型参数的位置。第10行就厉害了,它会生成一个SortedMap

执行MapperMethod.execute

MapperMethod.execute的代码如下:

Code 6

org.apache.ibatis.binding.MapperMethod

public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if (SqlCommandType.INSERT == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
} else if (SqlCommandType.UPDATE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
} else if (SqlCommandType.DELETE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
} else if (SqlCommandType.SELECT == this.command.getType()) {
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
} else {
if (SqlCommandType.FLUSH != this.command.getType()) {
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
result = sqlSession.flushStatements();
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}


代码对不同的情况进行不了不同的处理,把所有的情况分为下面这些类型:insert、delete、update、select、flust,在select中,又根据如何对返回值进行处理进行的划分,分别为:使用自定义ResultHandler处理、返回对象集合、返回Map、返回单个对象。下面我们不讨论flust的情况。

第23行可以看出来,调用了org.apache.ibatis.session.SqlSession.selectOne方法去操作数据库。关于org.apache.ibatis.session.SqlSession.selectOne方法类似方法是如何执行的可以参考上一篇博客。对于其他的情况,他们都是调用了org.apache.ibatis.session.SqlSession的方法,它们要做的有两点:1、选择适当的org.apache.ibatis.session.SqlSession方法进行调用;2.把Mapper方法传递进来的参数转化org.apache.ibatis.session.SqlSession对应方法支持的参数。我们知道org.apache.ibatis.session.SqlSession的参数就一个,要么是单个对象,要么集合,要么Map,二我们在调用Mapper方法时传递进来的是多个参数,我们JdbcProxy里以数组形式保存他们,所以要进行参数的转化。参数转化调用的都是一个方法,该方法的代码如下:

Code 7

org.apache.ibatis.binding.MapperMethod

public Object convertArgsToSqlCommandParam(Object[] args) {
int paramCount = this.params.size();
if (args != null && paramCount != 0) {
if (!this.hasNamedParameters && paramCount == 1) {
return args[((Integer)this.params.keySet().iterator().next()).intValue()];
} else {
Map<String, Object> param = new MapperMethod.ParamMap();
int i = 0;
for(Iterator i$ = this.params.entrySet().iterator(); i$.hasNext(); ++i) {
Entry<Integer, String> entry = (Entry)i$.next();
param.put(entry.getValue(), args[((Integer)entry.getKey()).intValue()]);
String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
param.put(genericParamName, args[((Integer)entry.getKey()).intValue()]);
}
}
return param;
}
} else {
return null;
}
}


第5行,当参数只有一个且参数没有org.apache.ibatis.annotations.Param注解时,就返回数组的数组的第一个值,也是调用Mapper方法的第一个值。其他情况是都返回Map。在构造Map时,使用到了本篇博客中前面提到的SortedMap。对于SortedMap每一个位置,这个位置的key为序号,value为org.apache.ibatis.annotations.Param注解的值(如果没有就是序号的字符串形式),我们需要把它转化成键值对的形式,键值对的key应该是xxxMapper.xml取值的key,那应该就是org.apache.ibatis.annotations.Param注解的值(如果没有就是序号的字符串形式),所以正好是SortedMap的value;键值对的value应该是我们调用Mapper方法传递进来的参数的对应位置的值。第11行代码就是这么做的。

最后

浓缩一下:

Mapper方法的执行过程:先获取Mapper对象,该对象是JdbcProxy代理对象。代理对象回调接口里,会根据Method,执行org.apache.ibatis.session.SqlSession对应的方法,同时需要完成参数的转化。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: