• mybatis源码解析8---执行mapper接口方法到执行mapper.xml的sql的过程


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

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

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

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

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

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

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

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

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

     1  @SuppressWarnings("unchecked")
     2     public <T> T getMapper(Class<T> type, SqlSession sqlSession)
     3     {
     4         // 从konwMappers获取MapperProxyFactory对象
     5         final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>)knownMappers.get(type);
     6         if (mapperProxyFactory == null)
     7         {
     8             throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
     9         }
    10         try
    11         {
    12             // 通过mapper代理工厂创建新实例
    13             return mapperProxyFactory.newInstance(sqlSession);
    14         }
    15         catch (Exception e)
    16         {
    17             throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    18         }
    19     }

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

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

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

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

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

     1 public Object execute(SqlSession sqlSession, Object[] args) {
     2         Object result;
     3         if (SqlCommandType.INSERT == command.getType()) {//如果执行insert命令
     4           Object param = method.convertArgsToSqlCommandParam(args);//构建参数
     5           result = rowCountResult(sqlSession.insert(command.getName(), param));//调用sqlSession的insert方法
     6         } else if (SqlCommandType.UPDATE == command.getType()) {//如果执行update命令
     7           Object param = method.convertArgsToSqlCommandParam(args);//构建参数
     8           result = rowCountResult(sqlSession.update(command.getName(), param));//调用sqlSession的update方法
     9         } else if (SqlCommandType.DELETE == command.getType()) {//如果执行delete命令
    10           Object param = method.convertArgsToSqlCommandParam(args);//构建参数
    11           result = rowCountResult(sqlSession.delete(command.getName(), param));//调用sqlSession的delete方法
    12         } else if (SqlCommandType.SELECT == command.getType()) {//如果执行select命令
    13           if (method.returnsVoid() && method.hasResultHandler()) {//判断接口返回类型,更加返回数据类型执行对应的select语句
    14             executeWithResultHandler(sqlSession, args);
    15             result = null;
    16           } else if (method.returnsMany()) {
    17             result = executeForMany(sqlSession, args);
    18           } else if (method.returnsMap()) {
    19             result = executeForMap(sqlSession, args);
    20           } else if (method.returnsCursor()) {
    21             result = executeForCursor(sqlSession, args);
    22           } else {
    23             Object param = method.convertArgsToSqlCommandParam(args);
    24             result = sqlSession.selectOne(command.getName(), param);
    25           }
    26         } else if (SqlCommandType.FLUSH == command.getType()) {
    27             result = sqlSession.flushStatements();
    28         } else {
    29           throw new BindingException("Unknown execution method for: " + command.getName());
    30         }
    31         if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    32           throw new BindingException("Mapper method '" + command.getName() 
    33               + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    34         }
    35         return result;
    36       }

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

    1   private final SqlCommand command;
    2   private final MethodSignature method;
    3 
    4   public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    5     this.command = new SqlCommand(config, mapperInterface, method);
    6     this.method = new MethodSignature(config, mapperInterface, method);
    7   }

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

     1 public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
     2         String statementName = mapperInterface.getName() + "." + method.getName();//接口方法名
     3         MappedStatement ms = null;
     4         if (configuration.hasStatement(statementName)) {
     5             //从configuration中根据接口名获取MappedStatement对象
     6           ms = configuration.getMappedStatement(statementName);
     7         } else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
     8             //如果该方法不是该mapper接口的方法,则从mapper的父类中找寻该接口对应的MappedStatement对象
     9           String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
    10           if (configuration.hasStatement(parentStatementName)) {
    11             ms = configuration.getMappedStatement(parentStatementName);
    12           }
    13         }
    14         if (ms == null) {
    15           if(method.getAnnotation(Flush.class) != null){
    16             name = null;
    17             type = SqlCommandType.FLUSH;
    18           } else {
    19             throw new BindingException("Invalid bound statement (not found): " + statementName);
    20           }
    21         } else {
    22           name = ms.getId();//设置name为MappedStatement的id,而id的值就是xml中对应的sql语句
    23           type = ms.getSqlCommandType();//设置type为MappedStatement的sql类型
    24           if (type == SqlCommandType.UNKNOWN) {
    25             throw new BindingException("Unknown execution method for: " + name);
    26           }
    27         }
    28       }

    到这里终于是看到了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方法的呢?下一篇继续分析......

  • 相关阅读:
    HTML 标题
    HTML 属性
    点云配准的端到端深度神经网络:ICCV2019论文解读
    人脸真伪验证与识别:ICCV2019论文解析
    人体姿态和形状估计的视频推理:CVPR2020论文解析
    FPGA最全科普总结
    深度人脸识别:CVPR2020论文要点
    视频教学动作修饰语:CVPR2020论文解析
    分层条件关系网络在视频问答VideoQA中的应用:CVPR2020论文解析
    慢镜头变焦:视频超分辨率:CVPR2020论文解析
  • 原文地址:https://www.cnblogs.com/jackion5/p/10597187.html
Copyright © 2020-2023  润新知