Mybatis源码解读(二)方法调用

上一章已经学习了初始化的过程,通过读取配置文件的形式已经获得了SqlSessionFactory,该对象持有Configuration

SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    ProductMapper productMapper = sqlSession.getMapper(ProductMapper.class);
    List<Product> productList = productMapper.selectProductList();
    for (Product product : productList) {
        System.out.printf(product.toString());
    }
} finally {
    sqlSession.close();
}

在调用mapper的方法之前,第一步一定是先创建一个sqlsession,然后根据获取的到sqlsession来获取mapper代理对象。这里就用到了初始化中的knowMappers,通过传入的ProductMapper.class获取到初始化时保存的MapperProxyFactory,并且通过mapperProxyFactory.newInstance(sqlSession);来获取mapperProxy代理对象

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

当调用了代理对象的某一个代理函数后,这个调用请求首先会被发送给代理对象处理类MapperProxy的invoke()函数,MapperProxy实现了InvocationHandler,重写其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);
  }

实际invoke方法在调用之前先缓存了method,保存在MapperProxy类的一个名为methodCache的Map中,并返回一个MapperMethod对象,然后开始调用excute方法。

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

可以看到excute方法根据不同操作调用了不同的方法,在这以select语句多条结果的sql为例。所以走的是

result = executeForMany(sqlSession, args);

executeForMany调用了sqlSession.<E>selectList方法来完成功能,实际是调用了sqlSessionProxy的代理

public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.<E> selectList(statement, parameter);
}

经过动态代理调用之后,来到了DefaultSqlSession的selectList方法

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

可以看到该方法从configuration获取了MappedStatement,这个方法是通过Map的get方法获取到对应的MappedStatement,获取到之后调用executor.query方法,获取到BoundSql即绑定的sql

 public BoundSql getBoundSql(Object parameterObject) {
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

在获取到BoundSql之后在进行excutor.query方法,这个方法的底层其实就是PreparedStatement,封装在下面这段代码的queryFromDatabase方法里面,内部的具体调用就不再详细讲解,所以说Mybatis就是底层jdbc的封装。

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

最后的结果其实是个list,结果以object返回。

最后来回顾一下调用过程,首先通过SqlSessionFactory获取sqlsession,通过sqlsession在knowMappers中查询MapperProxy代理对象,调用其invoke方法,该方法其实用调用了MapperMethod的invoke,最后这个方法的底层实现其实就是jdbc封装。


SqlSessionFactory和SqlSession的生命周期

SqlSessionFactory
一旦创建,SqlSessionFactory 将会存在于您的应用程序整个运行生命周期中。很少或根本

没有理由去销毁它或重新创建它。最佳实践是不要在一个应用中多次创建SqlSessionFactory。

这样做会被视为“没品味”。所是SqlSessionFactory 最好的作用域范围是一个应用的生命周期

范围。这可以由多种方式来实现,最简单的方式是使用Singleton 模式或静态Singleton 模式。

但这不是被广泛接受的最佳做法,相反,您可能更愿意使用像Google Guice 或Spring 的依赖注

入方式。这些框架允许您创造一个管理器,用于管理SqlSessionFactory 的生命周期。

SqlSession

每个线程都有一个SqlSession 实例,SqlSession 实例是不被共享的,并且不是线程安全

的。因此最好的作用域是request 或者method。决不要用一个静态字段或者一个类的实例字段来

保存SqlSession 实例引用。也不要用任何一个管理作用域,如Servlet 框架中的HttpSession,

来保存SqlSession 的引用。如果您正在用一个WEB 框架,可以把SqlSession 的作用域看作类似

于HTTP 的请求范围。也就是说,在收到一个HTTP 请求,您可以打开一个SqlSession,当您把

response 返回时,就可以把SqlSession 关闭。关闭会话是非常重要的,您应该要确保会话在一

个finally 块中被关闭。
 

原创文章 96 获赞 201 访问量 67万+

猜你喜欢

转载自blog.csdn.net/sinat_29774479/article/details/94738584