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語句操作


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM