Mybatis源码解析-MapperRegistry代理mapper接口

承接前文Spring mybatis源码篇章-MapperScannerConfigurer

前话

根据前文的分析我们可以得知Spring在使用MapperScannerConfigurer扫描DAO接口类集合时,会将相应的DAO接口封装成类型为org.mybatis.spring.mapper.MapperFactoryBean对象,并将相应的mapperInterface(dao接口)加入至mybatis框架中的org.apache.ibatis.session.Configuration对象里

configuration.addMapper(this.mapperInterface);

笔者深究下这个方法的调用,跟踪下去发现其实其调用的是org.apache.ibatis.binding.MapperRegistry#addMapper()方法。本文则通过这个方法进行展开讲解

MapperRegistry#addMapper()

直接查看相应的源码

  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中,并以MapperProxyFactory来进行包装
        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);
        }
      }
    }
  }

由上述的代码可知,所加入的mapperInterface正如其英文描述一样,必须是一个接口类。而且mybatis还将此接口类包装成org.apache.ibatis.binding.MapperProxyFactory对象,很有代理的味道~~~

MapperProxyFactory

上文讲了如何存放,那么获取的代码呢??如下所示

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	// 从map中获取相应的代理类
    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);
    }
  }

我们直接去翻看下MapperProxyFactory#newInstance()方法

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

粗粗一看,发现是最终是通过JDK动态代理来代理相应的mapper接口。而对应的代理处理则为java.lang.reflect.InvocationHandler接口的实现类org.apache.ibatis.binding.MapperProxy。接下来笔者针对这个类进行详细的分析

MapperProxy

首先看下构造函数

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
	// sql会话
    this.sqlSession = sqlSession;
    // mapper接口
    this.mapperInterface = mapperInterface;
    // 对应接口方法的缓存
    this.methodCache = methodCache;
  }

我们直接看下代理的实现方法invoke()

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

看来真正处理的是org.apache.ibatis.binding.MapperMethod类,我们继续分析

MapperMethod

也观察下构造函数

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
	// SqlCommand表示该sql的类型,一般为select|update|insert|delete|flush等类型
    this.command = new SqlCommand(config, mapperInterface, method);
    // method适配器,一般解析mapper接口对应method的参数集合以及回参等
    this.method = new MethodSignature(config, mapperInterface, method);
  }

继而简单的看下其execute()方法

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    // 根据command的类型进行CRUD操作
    switch (command.getType()) {
      case INSERT: {
     // 解析入参集合,@Param注解。详见ParamNameResolver#names注解
      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来进行真正的sql执行。我们可以简单看下sqlsession接口的方法

public interface SqlSession extends Closeable {

  <T> T selectOne(String statement);

  <T> T selectOne(String statement, Object parameter);

  <E> List<E> selectList(String statement);

  <E> List<E> selectList(String statement, Object parameter);

  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

  <K, V> Map<K, V> selectMap(String statement, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

  <T> Cursor<T> selectCursor(String statement);

  <T> Cursor<T> selectCursor(String statement, Object parameter);

  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);

  void select(String statement, Object parameter, ResultHandler handler);

  void select(String statement, ResultHandler handler);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

  int insert(String statement);
nsert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);

  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List<BatchResult> flushStatements();

  void close();

  void clearCache();

  Configuration getConfiguration();

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

  Connection getConnection();
}

提供数据库操作的CRUD方法~~很齐全

小结

作下简单的小结

1.mybatis对mapper接口的对应方法采取了JDK动态代理的方式

2.SERVICE或者CONTROLLER层调用mapper接口的时候,便会通过mapperRegistry去获取对应的mapperMethod来进行相应的SQL语句操作

posted @ 2017-07-28 17:15  南柯问天  阅读(3427)  评论(1编辑  收藏  举报