上一篇文章分析到mapper.xml中的sql标签对应的MappedStatement是如何初始化的,而之前也分析了Mapper接口是如何被加载的,那么问题来了,这两个是分别加载的到Configuration中的,那么问题来了,在使用过程中MappedStatement又是如何和加载的mapper接口进行关联的呢?本文将进行分析。

首先还是SqlSession接口的一个方法说起,也就是

<T> T getMapper(Class<T> type);

很显然这个方法是更加Class名获取该类的一个实例,而Mapper接口只定义了接口没有实现类,那么猜想可知返回的应该就是更加mapper.xml生成的实例了。具体是如何实现的呢, 先看下这个方法是如何实现的?

DefaultSqlSession实现该方法的代码如下:

 @Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}

方法很简单,调用了Configuration对象的getMapper方法,那么接下来再看下Configuration里面是如何实现的。代码如下:

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

调用了mapperRegistry的getMapper方法,参数分别是Class对象和sqlSession,再继续看MapperRegistry的实现,代码如下:

  @SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession)
{
// 从konwMappers获取MapperProxyFactory对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>)knownMappers.get(type);
if (mapperProxyFactory == null)
{
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try
{
// 通过mapper代理工厂创建新实例
return mapperProxyFactory.newInstance(sqlSession);
}
catch (Exception e)
{
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

可以看出是根据type从knowMappers集合中获取该mapper的代理工厂类,如何通过该代理工厂新建一个实例。再看下代理工厂是如何创建实例的,代码如下:

  @SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//通过代理获取mapper接口的新实例
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
} public T newInstance(SqlSession sqlSession) {
//创建mapper代理对象,调用newInstance方法
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}

那么现在我们就知道是如何根据Mapper.class来获取Mapper接口的实例的了,不过,到目前为止貌似还是没有看到和MappedStatement产生联系啊,别急,再往下看通过代理产生的mapper实例执行具体的方法是如何进行的。代码如下:

 @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断执行的方法是否是来自父类Object类的方法,也就是如toString、hashCode等方法
//如果是则直接通过反射执行该方法,如果不是Object的方法则再往下走,如果不加这个判断会发生什么呢?
//由于mapper接口除了定义的接口方法还包括继承于Object的方法,如果不加判断则会继续往下走,而下面的执行过程是从mapper.xml寻找对应的实现方法,
//由于mapper.xml只实现了mapper中的接口方法,而没有toString和hashCode方法,从而就会导致这些方法无法被实现。
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行mapperMethod对象的execute方法
return mapperMethod.execute(sqlSession, args);
} private MapperMethod cachedMapperMethod(Method method) {
//从缓存中根据method对象获取MapperMethod对象
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//如果mapperMethod为空则新建MapperMethod方法
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}

可以看出代理执行mapper接口的方法会先创建一个MapperMethod对象,然后执行execute方法,代码如下:

 public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {//如果执行insert命令
Object param = method.convertArgsToSqlCommandParam(args);//构建参数
result = rowCountResult(sqlSession.insert(command.getName(), param));//调用sqlSession的insert方法
} else if (SqlCommandType.UPDATE == command.getType()) {//如果执行update命令
Object param = method.convertArgsToSqlCommandParam(args);//构建参数
result = rowCountResult(sqlSession.update(command.getName(), param));//调用sqlSession的update方法
} else if (SqlCommandType.DELETE == command.getType()) {//如果执行delete命令
Object param = method.convertArgsToSqlCommandParam(args);//构建参数
result = rowCountResult(sqlSession.delete(command.getName(), param));//调用sqlSession的delete方法
} else if (SqlCommandType.SELECT == command.getType()) {//如果执行select命令
if (method.returnsVoid() && method.hasResultHandler()) {//判断接口返回类型,更加返回数据类型执行对应的select语句
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);
}
} else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
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;
}

可以看出大致的执行过程就是更加MapperMethod的方法类型,然后构建对应的参数,然后执行sqlSession的方法。到现在还是没有MappedStatement的影子,再看看MapperMethod是被创建的。

   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, mapperInterface, method);
}

这里涉及到了两个类SqlCommand和MethodSignature,先从SqlCommand看起。

 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String statementName = mapperInterface.getName() + "." + method.getName();//接口方法名
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
//从configuration中根据接口名获取MappedStatement对象
ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
//如果该方法不是该mapper接口的方法,则从mapper的父类中找寻该接口对应的MappedStatement对象
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
}
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
} else {
name = ms.getId();//设置name为MappedStatement的id,而id的值就是xml中对应的sql语句
type = ms.getSqlCommandType();//设置type为MappedStatement的sql类型
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}

到这里终于是看到了MappedStatement的身影,根据mapper的Class对象和method对象从Configuration对象中获取指定的MappedStatement对象,然后根据MappedStatement对象的值初始化SqlCommand对象的属性。而MethodSignature则是sql语句的签名,主要作用就是对sql参数与返回结果类型的判断。

总结:

1、sqlSession调用configuration对象的getMapper方法,configuration调用mapperRegistry的getMapper方法

2、mapperRegistry根据mapper获取对应的Mapper代理工厂

3、通过mapper代理工厂创建mapper的代理

4、执行mapper方法时,通过代理调用,创建该mapper方法的MapperMethod对象

5、MapperMethod对象的创建是通过从configuration对象中获取指定的MappedStatement对象来获取具体的sql语句以及参数和返回结果类型

6、调用sqlSession对应的insert、update、delete和select方法执行mapper的方法

到目前为止知道了mapper接口和mapper.xml是如何进行关联的了,也知道mapper接口是如何获取实例的了,也知道了mapper方法最终会调用SqlSession的方法,那么SqlSession又是如何具体去执行每个Sql方法的呢?下一篇继续分析......

mybatis源码解析8---执行mapper接口方法到执行mapper.xml的sql的过程的更多相关文章

  1. mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)

    目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ...

  2. MyBatis源码解析【7】接口式编程

    前言 这个分类比较连续,如果这里看不懂,或者第一次看,请回顾之前的博客 http://www.cnblogs.com/linkstar/category/1027239.html 修改例子 在我们实际 ...

  3. Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析

    在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例中我们谈到了properties,settings,envir ...

  4. Mybatis源码解析(三) —— Mapper代理类的生成

    Mybatis源码解析(三) -- Mapper代理类的生成   在本系列第一篇文章已经讲述过在Mybatis-Spring项目中,是通过 MapperFactoryBean 的 getObject( ...

  5. mybatis源码-解析配置文件(四)之配置文件Mapper解析

    在 mybatis源码-解析配置文件(三)之配置文件Configuration解析 中, 讲解了 Configuration 是如何解析的. 其中, mappers作为configuration节点的 ...

  6. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  7. mybatis源码-解析配置文件(三)之配置文件Configuration解析

    目录 1. 简介 1.1 系列内容 1.2 适合对象 1.3 本文内容 2. 配置文件 2.1 mysql.properties 2.2 mybatis-config.xml 3. Configura ...

  8. Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码

    在文章:Mybatis源码解析,一步一步从浅入深(一):创建准备工程,中我们为了解析mybatis源码创建了一个mybatis的简单工程(源码已上传github,链接在文章末尾),并实现了一个查询功能 ...

  9. Mybatis源码解析,一步一步从浅入深(六):映射代理类的获取

    在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中我们提到了两个问题: 1,为什么在以前的代码流程中从来没有addMapper,而这里却有getMapper? 2,UserDao ...

  10. Mybatis源码解析(一) —— mybatis与Spring是如何整合的?

    Mybatis源码解析(一) -- mybatis与Spring是如何整合的?   从大学开始接触mybatis到现在差不多快3年了吧,最近寻思着使用3年了,我却还不清楚其内部实现细节,比如: 它是如 ...

随机推荐

  1. 不能往Windows Server 2008 R2 Server中复制文件的解决方法

    目前一直直接往Windows 2008 R2 Server中复制文件(暂时还没有搭建ftp服务),突然不能复制了,于是百度找到了解决方法,特此记录(记忆). 1.在任务管理器中找到“rdpclip.e ...

  2. nodejs 学习三 异步和同步

    同步函数 for (let i = 0; i < 10; i ++) { setTimeout(() => { console.log(`${i} ______ ${new Date}`) ...

  3. 利用session防止表单重复提交

    转自:http://www.cnblogs.com/xdp-gacl/p/3859416.html 利用Session防止表单重复提交 对于[场景二]和[场景三]导致表单重复提交的问题,既然客户端无法 ...

  4. Linux个人知识扩展:服务器几u的意思,网络带宽

    服务器几u的意思: 指的服务器大小规格 1U=4.45cm 2U=8.9cm 3U=4.45cm * 3 4U=4.45cm * 4 这指的是服务器的高度 现在的服务器为节省空间都是很扁的 U是服务器 ...

  5. (转)面试必备技能:JDK动态代理给Spring事务埋下的坑!

    一.场景分析 最近做项目遇到了一个很奇怪的问题,大致的业务场景是这样的:我们首先设定两个事务,事务parent和事务child,在Controller里边同时调用这两个方法,示例代码如下: 1.场景A ...

  6. (转)Springboot邮件服务

    springboot仍然在狂速发展,才五个多月没有关注,现在看官网已经到1.5.3.RELEASE版本了.准备慢慢在写写springboot相关的文章,本篇文章使用springboot最新版本1.5. ...

  7. SQL IN查询优化

    实际项目中有如下SQL, 发现效率很低,用时超过1分钟 select TaskID, StartDate = min(UpdateTime), EndDate = max(UpdateTime) fr ...

  8. 我的Chrome插件

    1.AdBlock 用来屏蔽广告,用过的人都说好. 2.Flash Block(Plus) 用来限制Flash的播放. 3.Flash Control 用来限制Flash的播放. 4.Full Pag ...

  9. js快速排序算法解析

    数组的快速排序算法,和并归排序步骤基本类似. 都是先拆分,后合并.并归排序是:拆分容易,合并难. 快速排序是:拆分难,合并容易 要理解快速排序,首先要理解拆分逻辑 要素:找一个基准点,通过操作使得数列 ...

  10. 20165321 2017-2018-2《Java程序设计》课程总结

    每周作业链接汇总 预备作业1:20165321 我期望的师生关系 预备作业2:20165321 学习基础与C语言学习心得 预备作业3:20165321预备作业3:Linux安装及命令入门 第一周作业: ...