在前面两篇的MyBatis源码解读中,我们一路跟踪到了MapperProxy,知道了尽管是使用了动态代理技术使得我们能直接使用接口方法。为巩固加深动态代理,我们不妨再来回忆一遍何为动态代理。

我相信在初学MyBatis的时候几乎每个人都会发出一个疑问,为什么明明是XXXDao接口,我没有用任何代码实现这个接口,但却能直接使用这个接口的方法。现在清楚了,动态代理。我们来写一个demo小程序来看看。

首先是一个Test.java的接口,只有一个say方法。

 package day_16_proxy;

 /**
* @author 余林丰
*
* 2016年11月16日
*/
public interface Test {
void say();
}

我们现在想像MyBatis那样不用实现它而是直接调用。

test.say();

当然不可能直接实例化一个接口,此时就需要生成一个代理类TestProxy.java,这个类需实现InvocationHandler接口。

 package day_16_proxy;

 import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; /**
* @author 余林丰
*
* 2016年11月16日
*/
public class TestProxy implements InvocationHandler { /* (non-Javadoc)
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if (method.getName().equals("say")){
System.out.println("hello world");
}
return null;
} }

需要实现invoke方法,意思就是“调用”的意思(当然我们想要调用接口中的方法时并不会显示调用invoke)。我们从第19行看到,当调用的方法是say时,输出“hello world”。有了这个TestProxy.java代理类过后,我们再来客户端代码中测试。

 package day_16_proxy;

 import java.lang.reflect.Proxy;

 /**
* @author 余林丰
*
* 2016年11月16日
*/
public class Client { public static void main(String[] args){
Test test = (Test)Proxy.newProxyInstance(Test.class.getClassLoader(), new Class<?>[]{Test.class}, new TestProxy());
test.say();
}
}

在第14行代码中,我们已经能够直接调用Test接口中的say方法了,原因就在于我们通过Proxy.newProxyInstance方法生成了一个代理类实例即TestProxy。

回到我们的MyBatis源码,在上一节中我们知道了一个Dao接口实际上是通过MapperProxyFactory生成了一个MapperProxy代理类。

 //org.apache.ibatis.binding.MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

第3行代码是不是和在开头的例子中客户端测试代码如出一辙?生成代理类,这个代理类就是MapperProxy。清楚了MyBatis是如何构造出代理类的算是解决了第一个问题——一个接口怎么能直接调用其方法。

现在抛出第二个问题——接口中每个具体的方法是如何做到一一实现代理的呢?我们再来看看MapperProxy类。这次我们先看MapperProxy类实现的InvocationHanlder.invoke方法。

 //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 t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

第3行做一个判断,判断是否是一个类,如果是一个类,那么久直接传递方法和参数调用即可。但我们知道此时是一个接口(也可以自己实现接口,旧版本通常这样做)。如果不是一个类的话,就会创建一个MapperMethod方法。见名思意:好像就是这个类在执行我们所调用的每一个接口方法。最后返回的是MapperMethod.execute方法。暂时不予理会MapperProxy类中的cachedMapperMethod方法。

来看看MapperMethod类,这个MapperMethod类就不得了了啊,可以说它是统管所有和数据库打交道的方法(当然概括起来也只有insert、delete、update、select四个方法)。所以,不管你的dao层有多少方法,归结起来的sql语句都有且仅有只有insert、delete、update、select,可以预料在MapperMethod的execute方法中首先判断是何种sql语句。

//org.apache.ibatis.binding.MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
switch (command.getType()) {
case INSERT: {……}
case UPDATE: {……}
case DELETE: {……}
case SELECT: {……}
case FLUSH: {……}
}
}

这是MepperMethod.execute方法的删减,我们可以看到确实在execute方法内部首先判断是何种sql语句。(注意:在阅读这部分源代码时,我们的主线是MyBatis是如何创建出一个代理类,以及实现其方法的,而暂时忽略其中的细节)

我们选择常见的"SELECT"sql语句来进行解读,而在"SELECT"语句中又会设计到较多的细节问题:

 //org.apache.ibatis.binding
case 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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;

我们选取第7行中的executeForMany中的方法来解读试试看。

 //org.apache.ibatis.binding.MapperMethod
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
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;
}

第7行和第9行代码就是我们真正执行sql语句的地方,原来兜兜转转它又回到了sqlSession的方法中。在下一节,我们再重新回到重要的SqlSession中。

MyBatis源码解读(3)——MapperMethod的更多相关文章

  1. spring IOC DI AOP MVC 事务, mybatis 源码解读

    demo https://gitee.com/easybao/aop.git spring DI运行时序 AbstractApplicationContext类的 refresh()方法 1: pre ...

  2. MyBatis源码解读之延迟加载

    1. 目的 本文主要解读MyBatis 延迟加载实现原理 2. 延迟加载如何使用 Setting 参数配置 设置参数 描述 有效值 默认值 lazyLoadingEnabled 延迟加载的全局开关.当 ...

  3. Mybatis源码解读-SpringBoot中配置加载和Mapper的生成

    本文mybatis-spring-boot探讨在springboot工程中mybatis相关对象的注册与加载. 建议先了解mybatis在spring中的使用和springboot自动装载机制,再看此 ...

  4. Mybatis源码解读-插件

    插件允许对Mybatis的四大对象(Executor.ParameterHandler.ResultSetHandler.StatementHandler)进行拦截 问题 Mybatis插件的注册顺序 ...

  5. 【转】Mybatis源码解读-设计模式总结

    原文:http://www.crazyant.net/2022.html?jqbmtw=b90da1&gsjulo=kpzaa1 虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开 ...

  6. Mybatis源码解读-设计模式总结

    虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式. Mybatis至少 ...

  7. MyBatis源码解读(1)——SqlSessionFactory

    在前面对MyBatis稍微有点了解过后,现在来对MyBatis的源码试着解读一下,并不是解析,暂时定为解读.所有对MyBatis解读均是基于MyBatis-3.4.1,官网中文文档:http://ww ...

  8. MyBatis源码解读(4)——SqlSession(上)

    在上一篇博客中提到MyBatis是如何实现代理类MapperProxy,并抛出了一个问题--是怎么执行一个具体的sql语句的,在文末中提到了MapperMethod的execute采用命令模式来判断是 ...

  9. mybatis源码解读(一)——初始化环境

    本系列博客将对mybatis的源码进行解读,关于mybatis的使用教程,可以查看我前面写的博客——传送门. 为了便于后面的讲解,我们这里首先构造一个统一环境.也可以参考mybatis官网. 1.数据 ...

随机推荐

  1. 老李分享:大数据测试中java和hadoop关系

    Hadoop的创始人是Doug Cutting, 同时也是著名的基于Java的检索引擎库Apache Lucene的创始人.Hadoop本来是用于著名的开源搜索引擎Apache Nutch,而Nutc ...

  2. java代码打印打印杨辉三角

    郑州大学 徐峰 public class Print { void print(){ int[][] a=new int[6][6]; for(int i=0;i<a.length;i++){ ...

  3. linux常用脚本

    转载于http://justcoding.iteye.com/blog/1943504 我们在运维中,尤其是linux运维,都知道脚本的重要性,脚本会让我们的 运维事半功倍,所以学会写脚本是我们每个l ...

  4. 设计模式总结(Java)—— 适配器模式

    适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类.适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用.也就是说:当客户 ...

  5. web前端概念巩固(一)

    h5: 1.web语义化 Web语义化是指在进行HTML结构.表现.行为设计时,尽量使用语义化的标签,使程序代码简介明了,易于进行Web操作和网站SEO,方便团队协作的一种标准,以图实现一种" ...

  6. 基本数据结构——堆(Heap)的基本概念及其操作

    基本数据结构――堆的基本概念及其操作 小广告:福建安溪一中在线评测系统 Online Judge 在我刚听到堆这个名词的时候,我认为它是一堆东西的集合... 但其实吧它是利用完全二叉树的结构来维护一组 ...

  7. php写流程管理

    流程控制即某个人发起一个流程,通过一层一层审核,通过后,完成整个流程,若有一层审核未通过,中断整个流程.即结束! 比如请假流程: 某一员工发起一个请假流程,那么这个流程的节点人员即他的上级,上上级,上 ...

  8. 仿QQ空间和微信朋友圈,高解耦高复用高灵活

    先看看效果: 用极少的代码实现了 动态详情 及 二级评论 的 数据获取与处理 和 UI显示与交互,并且高解耦.高复用.高灵活. 动态列表界面MomentListFragment支持 下拉刷新与上拉加载 ...

  9. hadoop2.8和spark2.1完全分布式搭建

    一.前期准备工作: 1.安装包的准备: VMware(10.0版本以上) : 官方网站:https://www.vmware.com/cn.html 官方下载地址:http://www.vmware. ...

  10. hibernate 插入数据时让数据库默认值生效

    用hibernate做数据库插入操作时,在数据库端已经设置了对应列的默认值,但插入的数据一直为null.查找资料发现,原来是hibernate的配置项在作怪. Hibernate允许我们在映射文件里控 ...