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
"baseColumnList"/>
from
table1
where id = #{id}
select>
获取Mapper的主要代码如下:
Code 2
org.apache.ibatis.binding.MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
MapperProxy 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对象是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的代码如下:
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的代码如下:
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) {
EntryString> 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对应的方法,同时需要完成参数的转化。