Mybatis之Mapper调用源码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012734441/article/details/85673191

Mybatis之Mapper调用源码分析

这一篇是承接前面两篇的,分别为:Mybatis源码解析之配置加载(一), Mybatis源码解析之配置加载(二),前面两篇讲了在Mybatis启动时如何加载配置,这一节就讲在运行时,如何通过session获取Mapper代理类,从而实现对数据库的查询操作。

程序


首先展示下之前写的程序,main程序在第一篇中有展示,这里就不再完全的贴出来了,获取session并获取mapper类代码如下:

SqlSession session = sessionFactory.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);

        //执行查询返回一个唯一user对象的sql
        User user = userMapper.getUser(1);
        System.out.println(user);

获取session在这就不讲了,就是将configuration中的配置信息取出,然后实例化对应Executor类以及事务等相关信息,这里就直接从session中获取mapper类开始说。

session对象调用getMapper方法获取Mapper代理类,我们进入到getMapper方法。

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

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

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

在第三个getMapper方法中,从knownMappers中取出对应代理工厂类,knownMappers是一个HashMap对象,从第二篇文章中的addMapper方法得知,在加载mapper接口时,每一个Mapper接口类都被添加进knownMappers中,具体可以看以下一段代码,

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

继续回到之前的代码,在获取到MapperProxyFactory对象后,进行了实例化操作,继续分析,此处进入到newInstance方法中。

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

此处根据mapperInterface获取到Mapper的代理类对象,就是MapperProxy类。

当我们的演示程序main程序开始调用mapper中的方法时,即如下代码:

//执行查询返回一个唯一user对象的sql
User user = userMapper.getUser(1);

对应我们转到MapperProxy类的invoke方法。

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

我们直接看最下面的mapperMethod.execute(sqlSession, args),进入到execute方法中。

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;
  }

我们的演示程序中调用的方法为SELECT类型,同时我们要求返回的为resultMap,所以应该是最后一种方法调用,即else中的

Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);

其中的Object param = method.convertArgsToSqlCommandParam(args)

是将参数转换成kv结构的map返回,这里面就不再多看,直接到selectOne方法中。

@Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.<T>selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

转到了session中的selectOne方法,session中的selectOne还是调用了selectList方法,继续转到selectList方法。

@Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

selectList最终调用了**MappedStatement ms =configuration.getMappedStatement(statement);从之前的文章分析得知此处getMappedStatement实际上就是从mappedStatements.get(id)**中取出对应SQL,这里传进去的statement就是方法名,也就是id,因此此处也与前面串联起来。

接下来就是Executor执行sql的过程,我们进入到query方法中。

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

根据parameter获取到BoundSql,继续找query方法。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

这个query方法中只看list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql)这一行,其他的在日后分析cache的时候再回来继续看源码。

我们转到queryFromDatabase方法。

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

转向doQuery()方法,当然doQuery有多个实现类,这里简单点直接看SimpleExecutor,simpleExecutor中的doQuery实现如下:

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

这里的stmt = prepareStatement(handler, ms.getStatementLog());也不再详细讲述了,就是对参数的组装,以后讲其他的在此会涉及到,以后再说,直接看query方法。

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
  }

上述代码中最终是到了Statemet进行execute查询方法,而Statement对象即为java.sql包下的Statement对象,最终还是转换成了Statement对象查询,然后将返回结果进行了处理,从而返回一个list结果,然后有了上述代码中的selectOne方法的size判断。

if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }

如果返回多条则报错,如果返回一条则直接返回结果。

Mybatis调用方法源码分析就到这了,其中漏了很多重要的步骤的讲述,比如plugin的使用,参数的组装等等,这个到以后分析其他的时候再来讲述。

猜你喜欢

转载自blog.csdn.net/u012734441/article/details/85673191