mybatis源代碼分析之binding包


在使用ibatis執行數據庫訪問時,會調用形如

getSqlMapClientTemplate().queryForObject("getCityByCityId", cityId);

這樣的代碼。這樣的形式要求調用方選擇需要使用的函數(queryForObject、queryForList、update),還需要告訴這個函數具體執行哪一個statement(上文中是“getCityByCityId”),在這個過程中如果有一個地方選擇錯誤或者拼寫錯誤,不僅沒有辦法達到自己的期望值,可能還會出現異常,並且這種錯誤只有在運行時才能夠發現。

mybatis對此進行了改進,只要先聲明一個接口,就可以利用IDE的自動完成功能幫助選擇對應的函數,簡化開發的同時增加了代碼的安全性:

SqlSession session = sqlSessionFactory.openSession();
try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = 
mapper.selectBlog(101
);
} finally {
  session.close();
}

這個功能就是在利用java的動態代理在binding包中實現的。對動態代理不熟悉的讀者可以查看(http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html)。

一、binding包整體介紹

這個包中包含有四個類:

  1. BindingException 該包中的異常類
  2. MapperProxy   實現了InvocationHandler接口的動態代理類
  3. MapperMethod   代理類中真正執行數據庫操作的類
  4. MapperRegistry   mybatis中mapper的注冊類及對外提供生成代理類接口的類

二、MapperMethod

MapperProxy關聯到了這個類,MapperRegistry又關聯到了MapperProxy,因而這個類是這個包中的基礎類,我們首先介紹這個類的主要功能以及該類主要解決了那些問題。

這個類包含了許多屬性和多個函數,我們首先來了解下它的構造函數。

//declaringInterface  已定義的mapper接口
  //method 接口中具體的一個函數
  //sqlSession 已打開的一個sqlSession
  public MapperMethod(Class<?> declaringInterface, Method method, SqlSession sqlSession) {
    paramNames = new ArrayList<String>();
    paramPositions = new ArrayList<Integer>();
    this.sqlSession = sqlSession;
    this.method = method;
    this.config = sqlSession.getConfiguration();//當前的配置
    this.hasNamedParameters = false;
    this.declaringInterface = declaringInterface;
    setupFields();//確定這個方法在mapper中的全配置名:declaringInterface.getName() + "." + method.getName();
    setupMethodSignature();//下文進行詳細介紹
    setupCommandType();//設置命令類型,就是確定這個method是執行的那類操作:insert、delete、update、select
    validateStatement();
  }

在構造函數中進行了基本屬性的設置和驗證,這里面稍微復雜的操作是setupMethodSignature,我們來看其具體的功能:

//在具體實現時利用了Java的反射機制去獲取method的各項屬性
private void setupMethodSignature() {
    //首先判斷方法的返回類型,這里主要判斷三種:是否有返回值、返回類型是list還是map
    if( method.getReturnType().equals(Void.TYPE)){
      returnsVoid = true;
    }
    //isAssignableFrom用來判定兩個類是否存在父子關系;instanceof用來判斷一個對象是不是一個類的實例
    if (List.class.isAssignableFrom(method.getReturnType())) {
      returnsList = true;
    }
    if (Map.class.isAssignableFrom(method.getReturnType())) { 
      //如果返回類型是map類型的,查看該method是否有MapKey注解。如果有這個注解,將這個注解的值作為map的key
      final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
      if (mapKeyAnnotation != null) {
        mapKey = mapKeyAnnotation.value();
        returnsMap = true;
      }
    }
    
    //確定函數的參數
    final Class<?>[] argTypes = method.getParameterTypes();
    for (int i = 0; i < argTypes.length; i++) {
      //是否有為RowBounds類型的參數,如果有設置第幾個參數為這種類型的  
      if (RowBounds.class.isAssignableFrom(argTypes[i])) {
        if (rowBoundsIndex == null) {
          rowBoundsIndex = i;
        } else {
          throw new BindingException(method.getName() + " cannot have multiple RowBounds parameters");
        }
      } else if (ResultHandler.class.isAssignableFrom(argTypes[i])) {//是否有為ResultHandler類型的參數,如果有設置第幾個參數為這種類型的  
        if (resultHandlerIndex == null) {
          resultHandlerIndex = i;
        } else {
          throw new BindingException(method.getName() + " cannot have multiple ResultHandler parameters");
        }
      } else {
        String paramName = String.valueOf(paramPositions.size());
        //如果有Param注解,修改參數名;如果沒有,參數名就是其位置
        paramName = getParamNameFromAnnotation(i, paramName);
        paramNames.add(paramName);
        paramPositions.add(i);
      }
    }
  }

前面介紹了MapperMethod類初始化相關的源代碼,在初始化后就是向外提供的函數了,這個類向外提供服務主要是通過如下的函數進行:

public Object execute(Object[] args) {
    Object result = null;
    //根據初始化時確定的命令類型,選擇對應的操作
    if (SqlCommandType.INSERT == type) {
      Object param = getParam(args);
      result = sqlSession.insert(commandName, param);
    } else if (SqlCommandType.UPDATE == type) {
      Object param = getParam(args);
      result = sqlSession.update(commandName, param);
    } else if (SqlCommandType.DELETE == type) {
      Object param = getParam(args);
      result = sqlSession.delete(commandName, param);
    } else if (SqlCommandType.SELECT == type) {//相比較而言,查詢稍微有些復雜,不同的返回結果類型有不同的處理方法
      if (returnsVoid && resultHandlerIndex != null) {
        executeWithResultHandler(args);
      } else if (returnsList) {
        result = executeForList(args);
      } else if (returnsMap) {
        result = executeForMap(args);
      } else {
        Object param = getParam(args);
        result = sqlSession.selectOne(commandName, param);
      }
    } else {
      throw new BindingException("Unknown execution method for: " + commandName);
    }
    return result;
  }

這個函數整體上還是調用sqlSession的各個函數進行相應的操作,在執行的過程中用到了初始化時的各個參數。

三、MapperProxy

這個類繼承了InvocationHandler接口,我們主要看這個類中的兩個方法。一是生成具體代理類的函數newMapperProxy,另一個就是實現InvocationHandler接口的invoke。我們先看newMapperProxy方法。

public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
    //先初始化生成代理類所需的參數
    ClassLoader classLoader = mapperInterface.getClassLoader();
    Class<?>[] interfaces = new Class[]{mapperInterface};
    MapperProxy proxy = new MapperProxy(sqlSession);//具體要代理的類
    //利用Java的Proxy類生成具體的代理類
    return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
  }

我們再來看invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //並不是任何一個方法都需要執行調用代理對象進行執行,如果這個方法是Object中通用的方法(toString、hashCode等)無需執行
    if (!OBJECT_METHODS.contains(method.getName())) {
      final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
      //生成MapperMethod對象
      final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
      //執行MapperMethod對象的execute方法
      final Object result = mapperMethod.execute(args);
      //對處理結果進行校驗
      if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
        throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
      }
      return result;
    }
    return null;
  }

四、MapperRegistry

這個類會在Configuration類作為一個屬性存在,在Configuration類初始化時進行初始化:

protected MapperRegistry mapperRegistry = new MapperRegistry(this);

從類名就可以知道這個類主要用來mapper的注冊,我們就首先來看addMapper函數:

public void addMapper(Class<?> type) {
    //因為Java的動態代理只能實現接口,因而在注冊mapper時也只能注冊接口
    if (type.isInterface()) {
      //如果已經注冊過了,則拋出異常,而不是覆蓋
      if (knownMappers.contains(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        //先將這個mapper添加到mybatis中,如果加載過程中出現異常需要再將這個mapper從mybatis中刪除
        knownMappers.add(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);
        }
      }
    }
  }

下面我們來看下其向外提供生成代理對象的函數:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //如果不存在這個mapper,則直接拋出異常
    if (!knownMappers.contains(type))
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
      //返回代理類
      return MapperProxy.newMapperProxy(type, sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

五、代理類生成的順序圖

我們在開篇時提到了如下的代碼:

SqlSession session = sqlSessionFactory.openSession();
try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = 
mapper.selectBlog(101
);
} finally {
  session.close();
}

 

image


免責聲明!

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



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