MyBatis 3源码解析(二)

MapperProxy

到了这了大家可能还有一个疑问,我调用的是DAO接口中的方法,和上面这些好像没关系。别急接下来我们就来看看二者是怎么联系起来的

在mybatis和Spring集合使用中,使用DAO时我们一般使用@Autowired注入,但是大家有没有一个疑问,DAO是一个接口,接口是不能创建对象的,这个是怎么完成的呢?

Mybatis获取如何获取Mapper?

先来看第一个疑惑:使用mybatis操作数据库时,方法是这样的:

SqlSessionFactory sessionFactory = null;
String resource = "mybatis-conf.xml";  

sessionFactory = new SqlSessionFactoryBuilder().build(Resources
              .getResourceAsReader(resource));

SqlSession sqlSession = sessionFactory.openSession();
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println("jdk proxy mapper : "+mapper);
UserPO userPo = mapper.getUserByUsername("zhangsan");

如何获取SqlSession上面我们已经了解了,这里重点关注session.getMapper(UserMapper.class)这一句

上图描述了getMapper()这个方法的过程,我们来看MapperRegistry中的这个方法:

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

konwMapper会在创建MapperRegistry对象是初始化,key是我们定义的Dao的class对象,value是根据各Dao接口的class对象创建的MapperProxyFactory、接下来我们看MapperProxyFactory:


public class MapperProxyFactory<T> {
  //我们的Dao接口的class对象
  private final Class<T> mapperInterface;

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

到了这一步我们明白了,这是通过动态代理创建Dao接口的动态类的对象,而对接口所有方法的调用,最后都会回到调用mapperProxy的invoke方法上(这就是JDK动态代理)。我们去看看mapperProxy对象的invoke方法:


public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //判断你调用的是否是已实现的方法
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

}

if判断我们调用的方法是否是对象中的,我们调用的都是接口的方法,所以直接走mapperMethod.execute()。mapperMethod标识我们调用接口中的那个方法:

public class 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);
  }

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      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;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        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;
  }
}

好,这里就和上面的SqlSession接上了,最后这些操作都会归为SqlSession中的update、selectList、select操作了。而这其实我们已经知道了,最后SqlSession其实是交给Statement执行SQL命令了。

mybatis-spring如何获取Mapper?

下面来解决另外一个疑惑@Autowired注入的是个什么鬼?

注入的其实就是动态代理创建的接口实现类型的对象,不过mybatis-spring把sqlSession.getMapper()这个动作交个了MapperFactoryBean。而这个实在Spring扫描dao层的时候,为每一个接口分别创建一个MapperFactoryBean:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

}

mapperInterface就是dao的class对象,因为实现了FactoryBean接口,因此通过@Autowired获取对象时,实际是调用getObject方法获取对象,而这里有回到了sqlSession.getMapper()。到此终于和上面接上了

原文地址:https://www.cnblogs.com/xpz-python/p/10742473.html

时间: 2024-10-29 08:23:46

MyBatis 3源码解析(二)的相关文章

Spring 源码解析之HandlerAdapter源码解析(二)

Spring 源码解析之HandlerAdapter源码解析(二) 前言 看这篇之前需要有Spring 源码解析之HandlerMapping源码解析(一)这篇的基础,这篇主要是把请求流程中的调用controller流程单独拿出来了 解决上篇文章遗留的问题 getHandler(processedRequest) 这个方法是如何查找到对应处理的HandlerExecutionChain和HandlerMapping的,比如说静态资源的处理和请求的处理肯定是不同的HandlerMapping ge

chenglei1986/DatePicker源码解析(二)

接上一篇文章chenglei1986/DatePicker源码解析(一),我们继续将剩余的部分讲完,其实剩余的内容,就是利用Numberpicker来组成一个datePicker,代码非常的简单 为了实现自定义布局的效果,我们给Datepciker定制了一个layout,大家可以定制自己的layout <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="h

erlang下lists模块sort(排序)方法源码解析(二)

上接erlang下lists模块sort(排序)方法源码解析(一),到目前为止,list列表已经被分割成N个列表,而且每个列表的元素是有序的(从大到小) 下面我们重点来看看mergel和rmergel模块,因为我们先前主要分析的split_1_*对应的是rmergel,我们先从rmergel查看,如下 ....................................................... split_1(X, Y, [], R, Rs) -> rmergel([[Y, X

Mybatis 源码解析(二) - Configuration.xml解析

文章个人学习源码所得,若存在不足或者错误之处,请大家指出. 上一章中叙述了Configuration.xml流化到Mybatis内存中的过程,那么接下来肯定就是Configuration.xml文件解析操作,在Mybatis中,这个解析的操作由SqlSesssionFactoryBuilder负责.接下来我们看看SqlSessionFactoryBuilder的方法签名: SqlSessionFactoryBuilder提供了9个签名方法,其中前8个方法都是Configuration.xml的解

MyBatis 3源码解析(一)

Mybatis是支持定制化SQL.存储过程和高级映射的持久层框架.主要完成两件事: 封装JDBC的操作 利用反射完成Java类和SQL之间的转换 mybatis的主要目的就是管理执行SQL是参数的输入和输出,编写SQL和结果集的映射是mybatis的主要优点 mybatis中主要类和接口 Configuration:将mybatis配置文件中的信息保存到该类中 SqlSessionFactory:解析Configuration类中的配置信息,获取SqlSession SqlSession:负责和

AFNetworking2.0源码解析&lt;二&gt;

本篇我们继续来看看AFNetworking的下一个模块 — AFURLRequestSerialization. AFURLRequestSerialization用于帮助构建NSURLRequest,主要做了两个事情: 1.构建普通请求:格式化请求参数,生成HTTP Header. 2.构建multipart请求. 分别看看它在这两点具体做了什么,怎么做的. 1.构建普通请求 A.格式化请求参数 一般我们请求都会按key=value的方式带上各种参数,GET方法参数直接加在URL上,POST方

volley源码解析(二)--Request&lt;T&gt;类的介绍

在上一篇文章中,我们已经提到volley的使用方式和设计的整体思路,从这篇文章开始,我就要结合具体的源码来给大家说明volley功能的具体实现. 我们第一个要介绍的类是Request<T>这个一个抽象类,我将Request称为一个请求,通过继承Request<T>来自定义request,为volley提供了更加灵活的接口. Request<T>中的泛型T,是指解析response以后的结果.在上一篇文章中我们知道,ResponseDelivery会把response分派

jQuery 源码解析(二十五) DOM操作模块 html和text方法的区别

html和text都可以获取和修改DOM节点里的内容,方法如下: html(value)     ;获取匹配元素集合中的一个元素的innerHTML内容,或者设置每个元素的innerHTML内容,                ;value可选,可以是html代码或返回html代码的函数,如果没有参数则获取匹配元素集合中第一个元素的innerHTML内容 text(text)         ;获取匹配元素集合中所有元素合并后的文本内容,或者设置每个元素的文本内容,封装了createTextNo

第37篇 Asp.Net源码解析(二)--详解HttpApplication

这篇文章花了点时间,差点成烂到电脑里面,写的过程中有好几次修改,最终的这个版本也不是很满意,东西说的不够细,还需要认真的去看下源码才能有所体会,先这样吧,后面有时间把细节慢慢的再修改.顺便对于开发的学习,个人是觉得源码的阅读是最快的提高方式,当然阅读不是走马观花,应该多次阅读. 上次说到获得HttpApplication对象的创建,创建完成后调用InitInternal方法,这个方法任务比较多,也比较长,这里就不贴全码了,一个一个过程的去说: 初始化HttpModule 对于HttpModule